springSecurity 安全框架
基于Java 的安全框架主要有:Spring Security 和 Shiro
介绍基础概念
安全框架是对用户访问权限的控制,保证应用的安全性。
其主要的工作是用户认证 和 用户授权|鉴权
主要应用于 Spring 的企业应用系统,提供声明式的安全访问控制解决方案。
它提供了一组可以在 Spring 应用上下文中配置的Bean
能很好的结合 Spring 的DI 依赖注入和 AOP 面向切面编程功能
应用场景 ------单点登录 SSO(Single Sign On)
在分布式项目中会包含多个子项目,每个子项目都会接入认证和授权
需要实现用户只认证一次便可以在多个子项目中访问时不用再次认证
这个功能就叫做单点登陆。
从本质上讲,springSecurity是一组过滤器(称为过滤器链),依赖于Servlet过滤器,过滤器链
它们能够拦截客户的请求,并将这些请求转给认证管理器(AuthenticationManager)认证
和访问决策管理器处理(AccessDecisionManager) 来确定是否有权限访问
核心组件
ConSecurityContext ----安全上下文 ,主要持有Aurhentication 对象
SecurityContextHolder -------- 它持有的是安全上下文的信息。
UserDetails ------ 提供用户信息 (接口)
Authentication ------- 主体认证(信息) (接口)
我对security 的一些理解
其实UserDetails 是 springSecurity 提供的一个认证对象的接口,就是说,如果你要认证的是User,你就在 实现类里 封装进去。
UserDetailsService 认证服务,需要去实现最终认证的一个逻辑,在配置类中与认证提供者(AuthenticationProvider)相绑定。
流程上,可以分为 登录 ,请求认证/授权
在认证过滤器中(一般)通过判断 请求头中是否携带 token,来决定是否拦截,如果没有则认为未登录( ConSecurityContext 不会发生变化 )。
放行交给后续的过滤器,到了某个过滤器中,如果 SecurityContext 没有发生变化,则生成一个匿名的认证令牌,添加到 SecurityContext ,放行到请求的登录方法中。
(此处以 账号+密码 登录为例)用账号和密码生成 一个未认证的令牌,调用认证管理者的认证方法( authManager.authenticate(token) ) 进行认证,这个方法的底层源码,认证管理者会先使用Iterator
迭代获取出所有的认证提供者, 与认证提供者进行匹配,看看是否有认证提供者能够提供认证服务,去进行认证,认证成功后在提供者创建一个认证成功的信息类返回。其实其中还有经过许多步骤。
请求认证/授权
主要是对已经登录的用户请求其他资源时的流程。也是会进行认证,但是是生成未认证令牌放在SecurityContext 中进行一个认证。授权会去遍历用户所拥有的权限,去与明文配置的权限进行比对,或者是使用决策访问管理者(AccessDecisionManager)来做一个是否放行的判断,决定用户是否可以请求该资源。
配置类
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
// ----------------------装配区-------------------------
@Resource
private DaoAuthenticationProvider provider; //如果后续有多种登录方式,需要配置多个认证提供者
@Resource(name = "JWT")
private OncePerRequestFilter jwtAuthFilter; //jwt认证过滤器
@Resource
private AuthenticationEntryPoint authEntryPoint; //登录异常消息处理器
@Resource
private AccessDeniedHandler accessDeniedHandler; //权限异常消息处理器
@Autowired
private UserDetailsService service; //用户信息认证服务
@Autowired
private PermMapper permMapper;
//-----------------------配置组件bean-----------------------------------
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean //注意生成密码时 还没有设置加密编码 --- 已设置
public DaoAuthenticationProvider daoAuthenticationProvider(UserDetailsService service){
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); //创建编码器
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); //创建认证提供者
provider.setPasswordEncoder(encoder); //设置认证提供者编码器
provider.setUserDetailsService(service); //将认证提供者与认证服务绑定
return provider;
}
@Override //将认证提供者 加入 认证管理器
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(provider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests()
.antMatchers("/Common/login","/Common/register","/Common/loginWithBody").anonymous()
.antMatchers("/Common/welcome","/User/getMenu").permitAll();
setAuthentication(http);
http.addFilterBefore(jwtAuthFilter,
UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authEntryPoint);
}
private void setAuthentication( HttpSecurity http ) throws Exception {
ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry EIR = http.authorizeRequests();
for (Permission permission : permMapper.getAllChild()) {
EIR.antMatchers( permission.getMapping() ).hasAuthority(permission.getName());
System.out.println( permission.getName() +" " + permission.getMapping() );
}
EIR.anyRequest().authenticated();
}
}
UserDetailsServiceImpl
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
//由于将两次查表,密码的加密全都封装到 UserService 中的 getDTOByUsername(username);
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetailsDTO dto = userService.getDTOByUsername(username);
if( dto == null ){
throw new UsernameNotFoundException("用户不存在");
}
return new UserDetailsImpl(dto); //调试 成功
}
}
jwt认证过滤器
@Component(value = "JWT")
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Resource
private RedisTemplate template;
@Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse resp, FilterChain filterChain) throws ServletException, IOException {
String token = req.getHeader("token");
if(token == null){ //没有token 直接放行交给后面的过滤器处理
filterChain.doFilter(req,resp);
return;
}
//解析失败抛异常,放行交给后面的过滤器处理
User user = null;
user = JwtUtils.parseToken(token);
String KEY = SecurityUtils.makeKEY(user);
//判断redis 中的数据是否过期
Long expire = template.getExpire(KEY);
if( expire == null || expire == -2){
System.out.println("数据已经过期");
filterChain.doFilter(req,resp);
return;
}
//续时 ,取出
ValueOperations op = template.opsForValue();
UserDetailsDTO dto = (UserDetailsDTO)op.get(KEY);
op.set(KEY,dto,60*5, TimeUnit.SECONDS);
UserDetails userDetails = new UserDetailsImpl(dto);
//生成认证对象
Authentication authToken =
new UsernamePasswordAuthenticationToken
(userDetails, null, userDetails.getAuthorities());
//放入上下文中 进行认证
SecurityContextHolder.getContext().setAuthentication(authToken);
filterChain.doFilter(req,resp);
}
}
LoginServiceImpl
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private RedisTemplate template;
@Autowired
private AuthenticationManager authManager;
@Autowired
private TokenTemplate tokenTemplate;
@Override
public User login(String username, String password) {
//使用用户名加密码生成鉴别令牌
Authentication token =
new UsernamePasswordAuthenticationToken(username, password);
//调用鉴别管理器 进行鉴别 , 返回一个已鉴别的令牌
Authentication authenticate = authManager.authenticate(token);
//获取 认证对象
UserDetailsImpl details = (UserDetailsImpl)authenticate.getPrincipal();
//获取 dto
UserDetailsDTO dto = details.getUserDetailsDTO();
// 生成KEY 将dto 存入 redis ,key 为 生成的 KEY
tokenTemplate.saveTokenRedis(dto);
return dto.getUser();
}
}
UserDetailsImpl
public class UserDetailsImpl implements UserDetails {
private UserDetailsDTO userDetailsDTO;
private List authorities ; //所有的权限列表
// ----------------------构造器方法------------------------
public UserDetailsImpl() {}
public UserDetailsImpl(UserDetailsDTO userDetailsDTO) {
this.userDetailsDTO = userDetailsDTO;
setAuthorities();
}
//--------------------------方法----------------
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
public void setUserDetailsDTO(UserDetailsDTO userDetailsDTO) {
this.userDetailsDTO = userDetailsDTO;
}
//数据也从数据库中获取
public void setAuthorities() {
List perms = userDetailsDTO.getPerms();
List list = new ArrayList<>();
perms.forEach(perm -> {
list.add(new SimpleGrantedAuthority(perm));
});
userDetailsDTO.getUser().getRoles().forEach( role ->{
list.add(new SimpleGrantedAuthority(role.getName())); //已经 使用role.getName() 获取出角色名
} );
//--------------调错测试------------
list.forEach(System.out::println);
this.authorities = list;
}
public UserDetailsDTO getUserDetailsDTO() {
return userDetailsDTO;
}
@Override
public String getPassword() {
return userDetailsDTO.getUser().getPassword();
}
@Override
public String getUsername() {
return userDetailsDTO.getUser().getUsername();
}
//后面可以根据账号字段的值来动态控制
@Override
public boolean isAccountNonExpired() {
return true;
}//账号是否不过期
@Override
public boolean isAccountNonLocked() {
return true;
}//账号是否不锁定
@Override
public boolean isCredentialsNonExpired() {
return true;
}//账号 凭证(密码)是否不过期
@Override
public boolean isEnabled() {
return true;
}//账号是否启用
}