spring security介绍
1. 认识spring security
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
2. 安全框架的选择
其实大家已经了解过可以通过很多方式来保证我们系统的安全,例如 过滤器、拦截器、还有例如框架里类型的如 Apache shiro 和Spring 官方推荐的Spring Security,由于我们公司选择的是Spring Security,所以今天写些关于Spring Security相关的博客,以便今后好复习。
3. Spring Security原理
核心就是一组过滤器链,为了让我们更清楚的学习Spring Securty,我们有必要研究它主要的一些过滤器。
这个图也是在网上找的,其实在UsernamePasswordAuthenticationFilter得左边还有一个过滤器叫AbstractAuthenticationProcessingFilter,它是我们Spring Security过滤器的开始。
绿色的过滤器是我们可以定义的,绿色的ExceptionTranslationFilter 过滤器是Spring Security提供给我们处理异常的,橙色的FilterSecurityInterceptor 过滤器最后决定是否允许访问我们的资源。
为了更好的学习Spring Security,我们应该打断点跟踪一下源码。加入我们是根据最常用的用户名密码登陆
如下地方打断点。
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter # doFilter
大约193行
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter # attemptAuthentication
大约68行
org.springframework.security.web.access.ExceptionTranslationFilter # doFilter
大约130行
org.springframework.security.web.access.intercept.FilterSecurityInterceptor # invoke
大约120行
代码调试总结:
选择一个可访问的接口,发现先来到AbstractAuthenticationProcessingFilter接口,然后走了 org.springframework.security.web.access.ExceptionTranslationFilter#doFilter
为什么会先走这里,因为我们随意访问一个服务,都不满足前面几个认证过滤器的要求
3. 执行后到了org.springframework.security.web.access.intercept.FilterSecurityInterceptor#invoke 中的 InterceptorStatusToken token = super.beforeInvocation(fi); 执行后就报错了
4. 返回到了ExceptionTranslationFilter,并且被捕获到了异常信息 Access is denied (拒绝访问)
5. 处理异常信息之后根据配置返回到了登录页面
6. 登录之后,被拦截到 org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication
7. 认证成功之后,又经过了 ExceptionTranslationFilter
8. 接着又到了 FilterSecurityInterceptor 会验证是否有权限访问,如果有的话,InterceptorStatusToken token 会返回值,
9. 下一步放行,由于之前处理的是一个登录请求,所以会有一个重定向,到我们访问的api中
只要不是认证请求的话,前面的认证过滤器是不会走的,反而这两个类的流程都会走一遍 ExceptionTranslationFilter 、FilterSecurityInterceptor ; 看来这两个类是真的固定死的流程架子
4.自定义登陆功能
4.1 自定义获取用户信息逻辑
获取用户信息,Spring Security给我们提供了 org.springframework.security.core.userdetails.UserDetailsService
我们只需要继承该接口,重写 loadUserByUsername方法,我们就可以在该方法里面实现自己想做的事。例如我自已继承了 UserDetailsService并重重写了 loadUserByUsername,从数据库中获取用户信息,最后返回 org.springframework.security.core.userdetails.User
对象。
@Service
@Slf4j
public class UserDetailServiceImpl implements UserDetailsService{
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
com.huawang.hwbasic.pojo.User user = userMapper.findByUsername(username);
System.out.println(user);
log.info("----------->"+user);
return new User(username,user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
4.2 自定义登录
Spring Security 5.0之前默认是采用Http Basic登录方式,登录接口默认是 /login 并且是POST 请求。当然这些我们都是可以自己定义的。
Spring Security为我们提供了org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
抽象类
我们需要自己继承它并重写 configure(HttpSecurity http) 该configure 一共有三个重载方法,这时注意我们选择的是参数为HttpSecurity 的方法。
@Configuration
public class WebSecurityAdapter extends WebSecurityConfigurerAdapter {
/**
* 设置加密方式
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 登陆成功Handler
*/
@Autowired
private AuthenticationSuccessHandler successHandler;
/**
* 登陆失败Handler
*/
@Autowired
private AuthencationFailHalder failHalder;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin() //选择采用表单登录方式
.loginPage("/login.html") // 自定义login.html为自己的登录页面
.loginProcessingUrl("/form/login") // 设置处理的登录请求为 /form/login ,Spring Security默认是 /login
.successHandler(successHandler) // 添加登录成功处理Handle
.failureHandler(failHalder) // 添加登录失败处理Handle
.and()
.authorizeRequests()
.antMatchers("/login.html","/form/login").permitAll() // /login.html ,/form/login 请求匿名账号允许访问
.anyRequest() //匹配所有的请求,未登录用户不允许访问
.authenticated()
.and().csrf().disable(); // 关闭csrf,在开发中建议关闭掉
}
4.3 自定义登录处理逻辑
4.3.1 登录成功处理
自定义登录处理逻辑,Spring Security给我们提供了org.springframework.security.web.authentication.AuthenticationSuccessHandler
我们只需要实现它,并定义自己的代码既可以了。
@Component
public class LoginSuccessHalder implements AuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
4.3.2 自定义登录失败处理逻辑
同理处理登录失败逻辑,Spring Security给我们提供了 org.springframework.security.web.authentication.AuthenticationFailureHandler
@Component
public class AuthencationFailHalder implements AuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(exception));
}
}
虽然这个自定义登录的Demo非常简单,但是在实际的项目中,我们可以拿着它拓展自己想要的安全案例。