spring security详解
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 } }
如果只实现一个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 }
然后实现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();//关闭跨站请求伪造攻击拦截 }
5.动态配置登录页
.做一个我们自己默认的登录页,如果不想用默认的也可以动态配置。使用到的注解@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();//关闭跨站请求伪造攻击拦截 }
登陆失败就会返回500 登陆异常信息