Spring Security 是 Spring家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。本文为学习三更草堂B站视频做个笔记便于回顾。
引入依赖
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-data-redis
com.alibaba
fastjson
1.2.78
com.auth0
java-jwt
4.0.0
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工具类在之前的文章存有,这里省略。
SpringSecurity配置类
@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 login1(@RequestBody User user){
Map 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("用户认证失败");
}
}