Spring Security 是 Spring家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。本文为学习三更草堂B站视频做个笔记便于回顾。
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.78</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.0.0</version> </dependency>
SpringSecurity完整流程
UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。
ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException。
FilterSecurityInterceptor:负责权限校验的过滤器。
认证流程详解
Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。
AuthenticationManager接口:定义了认证Authentication的方法
UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。
UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。
自定义登录接口
调用ProviderManager的方法进行认证 如果认证通过生成jwt。把用户信息存入redis中,自定义UserDetailsService。在这个实现类中去查询数据库。
校验:
定义Jwt认证过滤器
获取token解析token获取其中的userid,从redis中获取用户信息存入SecurityContextHolder。
JWT,Redis工具类在之前的文章存有,这里省略。
@Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; @Autowired Au au; @Bean PasswordEncoder password(){ return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/alipay/pay").permitAll() .antMatchers("/alipay/notify").permitAll() .antMatchers("/user/login").permitAll() .antMatchers("/register").permitAll().anyRequest().authenticated(); http.cors();//允许跨域 http.exceptionHandling().authenticationEntryPoint(au);//配置认证失败处理器 http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } }
登录接口
@PostMapping("/user/login") public Map<String,Object> login1(@RequestBody User user){ Map<String,Object> map=new HashMap<>(); UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(user.getLoginname(),user.getPassword()); Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken); if(authenticate==null){ map.put("list","error"); } else { LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); Long id = loginUser.getUser().getId(); String token = JwtUtil.generateToken(id); // redisUtil.set(String.valueOf(id), loginUser.getUser()); map.put("list","success"); map.put("token",token); } return map; }
LoginUser实体类
@Data @AllArgsConstructor @NoArgsConstructor public class LoginUser implements UserDetails, Serializable { private User user; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getLoginname(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
UserDetailsService接口
@Service public class UserDetailServiceImpl implements UserDetailsService { @Autowired UserMapper userMapper; @Override public UserDetails loadUserByUsername(String loginname) throws UsernameNotFoundException { User user = userMapper.selectOneByLoginname(loginname); if(user==null){ throw new RuntimeException("用户名或者密码错误"); } return new LoginUser(user); } }
设置拦截器,这里的代码做了变动,因为在原先的代码,登录之后token过期不退出登录然后重新登陆就会报异常错误。
@Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired RedisUtil redisUtil; @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { String token = httpServletRequest.getHeader("token"); if(!StringUtils.hasText(token)){ filterChain.doFilter(httpServletRequest,httpServletResponse); return; } Long userId; try { DecodedJWT decodedJWT = JwtUtil.decodeToken(token); userId = decodedJWT.getClaim("userId").asLong(); }catch (Exception e){ e.printStackTrace(); filterChain.doFilter(httpServletRequest,httpServletResponse); return; } // User user =(User) redisUtil.get(String.valueOf(userId)); // if(Objects.isNull(user)){ // filterChain.doFilter(httpServletRequest,httpServletResponse); // return; // } UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(null,null,null); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); filterChain.doFilter(httpServletRequest,httpServletResponse); } }
设置认证失败处理器
@Component public class Au implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().print("用户认证失败"); } }