SpringSecurity整合JWT实现认证和授权
文章目录
- SpringSecurity整合JWT实现认证和授权
- 前言
- 一、SpringSecurity介绍和架构分析及使用流程
- 使用流程
- 二、效果展示
- 三、代码实现
- 总结
前言
本文主要讲解l通过整合SpringSecurity和JWT实现后台用户的登录和授权功能,使用到的技术有nacos,dubbo,SpringSecurity,redis.
一、SpringSecurity介绍和架构分析及使用流程
SpringSecurity是一个安全框架,支持自定义需求。同其他安全框架一样,SpringSecurity核心功能就是认证和授权。
SpringSecurity的核心架构就是三大过滤器
UsernamePasswordAuthenticationFilter:用于认证
ExceptionTranslationFilter:用于处理异常
FilterSecurityIntercepter:用于授权
概念速查:
Authentication:表示当前访问的用户,封装了用户相关信息
AuthenticationManager:定义认证Authentication的方法
UserDetailsSetvice接口:定义了一个根据用户名查找用户的方法,需要我们自己去实现
UserDetails接口:提供核心用户信息。通过UserDetailsSetvice接口的根据用户名获取用户方法得到一个UserDetails对象返回,然后将这些信息封装到Authentication。
使用流程
登录:
- 自定义登录接口
调用ProviderManager的方法进行认证,如果认证通过生成jwt
把用户信息存入redis中 - 自定义UserDetailsService
实现根据用户名查找用户的方法
校验: - 自定义jwt认证过滤器
获取token,解析token获取其中的userid,从redis中获取用户信息,存入SecurityContextHolder
二、效果展示
登录
带着token访问其他接口
如果某接口需要某种权限,如果这个用户没有这个权限,那么就就会被权限过滤器拒绝
登出:
三、代码实现
封装UserDetails对象
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
private List<String> permissions;
public LoginUser(User user, List<String> permissions) {
this.user = user;
this.permissions = permissions;
}
@JSONField(serialize = false)
private List<SimpleGrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if(authorities!=null){
return authorities;
}
authorities = permissions.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.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;
}
}
实现UserDetailsService接口,这里使用Dubbo远程调用会员服务从数据库查用户信息并返回封装成LoginUser对象
public class UserDetailsServiceImpl implements UserDetailsService {
@DubboReference
private MemberService MemberService;
@Override
public UserDetails loadUserByUsername(String username) {
//远程调用查询用户信息
User user = null;
try {
user = MemberService.findMemberByUsername(username);
} catch (InterruptedException e) {
System.out.println("服务器超时");
}
//TODO 查询用户权限信息
//把数据封装成UserDetails返回
Long id=user.getId();
List<String> perms=MemberService.findPermsByUserId(id);
return new LoginUser(user,perms);
}
}
自定义jwt过滤器
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
//放行
filterChain.doFilter(request, response);
return;
}
//解析token
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
System.out.println("token非法");
throw new RuntimeException("token非法");
}
//从redis中获取用户信息
String redisKey = "login:" + userid;
String user = redisTemplate.opsForValue().get(redisKey);
LoginUser loginUser = JSONObject.parseObject(user,LoginUser.class);
if(Objects.isNull(loginUser)){
throw new RuntimeException("用户未登录");
}
//存入SecurityContextHolder
//TODO 获取权限信息封装到Authentication中
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request, response);
}
}
实现注册登录登出业务逻辑
public CommonResult<Map> login(String name, String password) {
//AuthenticationManager authenticate进行认证
// 把用户名和密码交给manager,此Manager会调用UserDetailsService从数据库获取数据来对比认证。
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(name, password);
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//如果认证没通过,给出对应的提示
if (Objects.isNull(authenticate)) {
throw new RuntimeException("登录失败");
}
//如果认证通过,使用UserId生成jwt,jwt作为result返回
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String uid = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(uid);
//把完整的用户信息存入redis,UserId做为key
String s = JSONObject.toJSONString(loginUser);
redisTemplate.opsForValue().set("login:" + uid, s, 12, TimeUnit.HOURS);
Map<String, String> map = new HashMap<>();
map.put("token", jwt);
return CommonResult.success(map);
}
@Override
public CommonResult<String> logout() {
//获取SecurityContextHolder中的用户id
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
Long userid = loginUser.getUser().getId();
//删除redis中的值
String redisKey = "login:" + userid;
redisTemplate.delete(redisKey);
return CommonResult.success("注销成功");
}
@Override
public CommonResult<String> register(String name, String password) {
String username = MemberService.registerMember(name, password);
if ("null".equals(username)) {
return CommonResult.failed("用户名:" + name + "已存在,请重新注册");
}
return CommonResult.success("注册成功," + name);
}
配置过滤器链
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
// @Autowired
// private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
//创建BCryptPasswordEncoder注入容器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// // 对于登录注册接口 允许匿名访问
.antMatchers("/auth/login").anonymous()
.antMatchers("/auth/register").anonymous()
//设置需要权限的接口
.antMatchers("/auth/hello2").hasAuthority("haha")
// // 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
//添加过滤器,放在UsernamePasswordAuthenticationFilter之前,否则jwt过滤器就没意义了
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//
// //配置异常处理器
http.exceptionHandling()
//配置认证失败处理器
// .authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
//允许跨域
http.cors();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
总结
以上就是使用SpringSecurity结合jwt实现认证和校验的具体实现,核心就是理解SpringSecurity的过滤器链