springsecurity详解
1.springsecurity
springsecurity底层实现为一条过滤器链,就是用户请求进来,判断有没有请求的权限,抛出异常,重定向跳转。
2.登录页
springsecurity自带一个登录页。
从登陆入手,登录页替换成我们自己的,对输入的账号密码进行验证
/**
* 表单登陆security
* 安全 = 认证 + 授权
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//以下五步是表单登录进行身份认证最简单的登陆环境
http.formLogin() //表单登陆 1
.and() //2
.authorizeRequests() //下面的都是授权的配置 3
.anyRequest() //任何请求 4
.authenticated(); //访问任何资源都需要身份认证 5
}
}
image.png
如果只实现一个WebSecurityConfigurerAdapter然后重写一下configure方法,效果会默认使用springsecurity的登录页 ,以及项目启动时后台会打印出一个默认的密码,然后使用任意账号就可以进行登录访问指定的资源
3.自定义登录页 与 UserDetailsService 用户名密码校验
如果想要使用自己的登录页 并且用户名密码是自己数据库中的,进一步完善spring security认证体系,首先需要做以下配置。
@Override
protected void configure(HttpSecurity http) throws Exception {
//以下五步是表单登录进行身份认证最简单的登陆环境
http.formLogin() //表单登陆 1
.loginPage("/login.html") //指定登陆页面
.and() //2
.authorizeRequests() //下面的都是授权的配置 3
.antMatchers("/login.html").permitAll()//访问此地址就不需要进行身份认证了,防止重定向死循环
.anyRequest() //任何请求 4
.authenticated(); //访问任何资源都需要身份认证 5
}
image.png
然后实现UserDetailsService接口进行用户姓名密码校验 (由于springboot2.x中security是5.x版本的,所以这里的密码是默认做了BCrypt加密的,就需要bean一个BCrypt)
@Component
public class MyUserDetailService implements UserDetailsService {
//注入mapper
//...
@Autowired
private PasswordEncoder passwordEncoder;
private Logger LOG = LoggerFactory.getLogger(MyUserDetailService.class);
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
LOG.error("登陆用户输入的用户名:{}",s);
//根据用户名查找用户信息
//密码进行bcrypt加密
String pwd = "wangkai";
//String cryptPwd = BCrypt.hashpw(pwd, BCrypt.gensalt());
String cryptPwd = passwordEncoder.encode(pwd);
LOG.error("加密后的密码为: {}",cryptPwd);
return new User("s",cryptPwd, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); //账号 密码 权限
}
}
/**
* 表单登陆security
* 安全 = 认证 + 授权
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 介绍
* springboot2.x引入的security版本是5.x的,这个版本需要提供一个PasswordEncoder实例,不然就会报错
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//以下五步是表单登录进行身份认证最简单的登陆环境
http.formLogin() //表单登陆 1
.loginPage("/login.html") //指定登陆页面
.and() //2
.authorizeRequests() //下面的都是授权的配置 3
.antMatchers("/login.html").permitAll()//访问此地址就不需要进行身份认证了,防止重定向死循环
.anyRequest() //任何请求 4
.authenticated(); //访问任何资源都需要身份认证 5
}
}
4.登陆页面提交页面 /authentication/form
添加登陆页面提交页面,关闭跨站请求伪造攻击,登陆访问资源
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登陆</title>
</head>
<body>
<h2>标准登陆页面</h2>
<h3>表单登陆</h3>
<form action = "/authentication/form" method ="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2"><button type="submit">登陆</button></td>
</tr>
</table>
</form>
</body>
</html>
@Override
protected void configure(HttpSecurity http) throws Exception {
//以下五步是表单登录进行身份认证最简单的登陆环境
http.formLogin() //表单登陆 1
.loginPage("/login.html") //指定登陆页面
.loginProcessingUrl("/authentication/form")//登陆页面提交的页面 开始使用UsernamePasswordAuthenticationFilter过滤器处理请求
.and() //2
.authorizeRequests() //下面的都是授权的配置 3
.antMatchers("/login.html").permitAll()//访问此地址就不需要进行身份认证了,防止重定向死循环
.anyRequest() //任何请求 4
.authenticated() //访问任何资源都需要身份认证 5
.and()
.csrf().disable();//关闭跨站请求伪造攻击拦截
}
image.png
5.动态配置登录页
image.png.做一个我们自己默认的登录页,如果不想用默认的也可以动态配置。使用到的注解@ConfigurationProperties。
.增加接口/authentication/require
.引导用户进入登录页登陆
@Override
protected void configure(HttpSecurity http) throws Exception {
//以下五步是表单登录进行身份认证最简单的登陆环境
http.formLogin() //表单登陆 1
//.loginPage("/login.html") //指定登陆页面
.loginPage("/authentication/require")
.loginProcessingUrl("/authentication/form")//登陆页面提交的页面 开始使用UsernamePasswordAuthenticationFilter过滤器处理请求
.and() //2
.authorizeRequests() //下面的都是授权的配置 3
.antMatchers("/login.html",
"/authentication/require",
securityProperties.getBrowser().getLoginPage()).permitAll()//访问此地址就不需要进行身份认证了,防止重定向死循环
.anyRequest() //任何请求 4
.authenticated() //访问任何资源都需要身份认证 5
.and()
.csrf().disable();//关闭跨站请求伪造攻击拦截
}
@RestController
public class BrowserSecurityController {
private Logger LOG = LoggerFactory.getLogger(BrowserSecurityController.class);
//将当前请求缓存到session里
private RequestCache requestCache = new HttpSessionRequestCache();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Resource
private SecurityProperties securityProperties;
/**
* 当需要身份认证时跳转到这里
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/authentication/require",method = RequestMethod.GET)
@ResponseStatus(code = HttpStatus.UNAUTHORIZED) //未授权状态码
public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
//拿到引发跳转的请求
SavedRequest savedRequest = requestCache.getRequest(request,response);
if(savedRequest != null){
String targetUrl = savedRequest.getRedirectUrl();
String fileUrl=new URL(targetUrl).getFile();
LOG.info("引发跳转的请求是:{}",targetUrl);
if(StringUtils.endsWithIgnoreCase(targetUrl,".html") || fileUrl.equals("/")){
//调转到登录页 》》这里登录页做成可配置的
redirectStrategy.sendRedirect(request,response,securityProperties.getBrowser().getLoginPage());
}
}
return new SimpleResponse("访问资源需要登陆,请访问登陆页面");
}
}
从配置文件中读取当访问资源需要身份认证调转的页面地址
server.port=8888
#自定义springsecurity 登录页面
security.browser.loginPage = /mylogin.html
package com.example.security.properties;
import com.example.security.pojo.SecurityBrowserPojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 实现动态配置用户专属登陆页面
*/
@ConfigurationProperties(prefix = "security")
public class SecurityProperties {
private SecurityBrowserPojo browser = new SecurityBrowserPojo();
public SecurityBrowserPojo getBrowser() {
return browser;
}
public void setBrowser(SecurityBrowserPojo browser) {
this.browser = browser;
}
}
public class SecurityBrowserPojo {
//设置默认地址
private String loginPage = "/login.html";
public String getLoginPage() {
return loginPage;
}
public void setLoginPage(String loginPage) {
this.loginPage = loginPage;
}
}
package com.example.security.config.securityconfig;
import com.example.security.properties.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties({SecurityProperties.class}) //设置注解读取生效 (试了下不用配置这里@ConfigurationProperties也可以生效)
public class SecurityPropertiesConfig {
}
5.登陆成功/登陆失败处理
某些时候用户登陆成功,登陆失败的时候可能还需要做一些操作,比如成功登陆增加一积分之类的操作,这里需要做两个handler处理器
/**
* 设置通过请求拦截。登陆成功后处理
*/
@Component("wawAuthenticationSuccessHandler")
public class WawAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private Logger LOG = LoggerFactory.getLogger(WawAuthenticationSuccessHandler.class);
@Resource
private ObjectMapper objectMapper;
/**
* @param authentication 封装认证信息>>用户信息 请求ip之类的
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
LOG.info("登陆成功");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
/**
* 设置通过请求拦截。登陆失败后处理
*/
@Component("wawAuthenticationFailHandler")
public class WawAuthenticationFailHandler implements AuthenticationFailureHandler{
private Logger LOG = LoggerFactory.getLogger(WawAuthenticationFailHandler.class);
@Resource
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
LOG.info("登陆失败");
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(e));
}
}
成功与失败的处理器 配置到配置信息中
@Override
protected void configure(HttpSecurity http) throws Exception {
//以下五步是表单登录进行身份认证最简单的登陆环境
http.formLogin() //表单登陆 1
//.loginPage("/login.html") //指定登陆页面
.loginPage("/authentication/require")
.loginProcessingUrl("/authentication/form")//登陆页面提交的页面 开始使用UsernamePasswordAuthenticationFilter过滤器处理请求
.successHandler(wawAuthenticationSuccessHandler)
.failureHandler(wawAuthenticationFailHandler)
.and() //2
.authorizeRequests() //下面的都是授权的配置 3
.antMatchers("/authentication/require",
"/login.html",
securityProperties.getBrowser().getLoginPage()).permitAll()//访问此地址就不需要进行身份认证了,防止重定向死循环
.anyRequest() //任何请求 4
.authenticated() //访问任何资源都需要身份认证 5
.and()
.csrf().disable();//关闭跨站请求伪造攻击拦截
}
image.png登陆失败就会返回500 登陆异常信息