• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

SpringSecurity整合jwt权限认证的全流程讲解

java 搞代码 4年前 (2022-01-05) 81次浏览 已收录 0个评论
文章目录[隐藏]

这篇文章主要介绍了SpringSecurity整合jwt权限认证的全流程讲解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

JWT

本文代码截取自实际项目。

jwt(Json Web Token),一个token,令牌。

简单流程:

用户登录成功后,后端返回一个token,也就是颁发给用户一个凭证。之后每一次访问,前端都需要携带这个token,后端通过token来解析出当前访问对象。

优点

1、一定程度上解放了后端,后端不需要再记录当前用户是谁,不需要再维护一个session,节省了开销。

2、session依赖于cookie,某些场合cookie是用不了的,比如用户浏览器cookie被禁用、移动端无法存储cookie等。

缺点

1、既然服务器通过token就可以知道当前用户是谁,说明其中包含了用户信息,有一定的泄露风险。

2、因为是无状态的,服务器不维持会话,那么每一次请求都会重新去数据库读取权限信息,性能有一定影响。

(如果想提高性能,可以将权限数据存到redis,但是如果用redis,就已经失去了jwt的优点,直接用普通的token+redis即可)

3、只能校验token是否正确,通过设定过期时间来确定其有效性,不可以手动注销。

先说怎么做,再说为什么。

代码

依赖

  org.springframework.bootspring-boot-starter-security2.3.3.RELEASE io.jsonwebtokenjjwt0.9.1

jwt工具类

 public class JwtTokenUtils implements Serializable { private static final long serialVersionUID = -3369436201465044824L; //生成token public static String createToken(String username) { return Jwts.builder().setSubject(username) .setExpiration(new Date(System.currentTimeMillis()+ Constants.Jwt.EXPIRATION)) .signWith(SignatureAlgorithm.HS512, Constants.Jwt.KEY).compressWith(CompressionCodecs.GZIP).compact(); } //获取用户名 public static String getUserName(String token) { return Jwts.parser().setSigningKey(Constants.Jwt.KEY).parseClaimsJws(token).getBody().getSubject(); } }

涉及常量

 public interface Constants { public interface Jwt{ /** * 密钥 */ String KEY = "123123"; /** * 过期时间 */ long EXPIRATION = 7200000; /** * 请求头 */ String TOKEN_HEAD = "Authorization"; } }

实体类和Service

为了减少对实体类的入侵,我又定义了一个对象

原实体类

 /** * 用户信息 * */ @Data @TableName("tb_user_info") public class UserInfo implements Serializable { private static final long serialVersionUID = 1L; /** * 主键id */ @TableId(type = IdType.AUTO) private Integer id; /** * 登陆账号 */ private String loginName; /** * 显示名 */ private String dispName; /** * 密码 */ private String password; /** * 状态,1正常,2禁用 */ private Byte status; /** * 创建人 */ @TableField(fill = FieldFill.INSERT) private Integer createBy; /** * 更新人 */ @TableField(fill = FieldFill.INSERT_UPDATE) private Integer updateBy; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private Date createTime; /** * 更新时间 */ @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; /** * 1正常, 0 删除 */ @TableLogic private Byte deleted; }

定义对象

 @Data public class UserSecurity implements UserDetails { private static final long serialVersionUID = -6777760550924505136L; private UserInfo userInfo; private List permissionValues; public UserSecurity(UserInfo userInfo) { this.userInfo = userInfo; } @Override public Collection getAuthorities() { Collection authorities = new ArrayList(); for(String permissionValue : permissionValues) { if(StringUtils.isEmpty(permissionValue)) continue; SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue); authorities.add(authority); } return authorities; } @Override public String getPassword() { return userInfo.getPassword(); } @Override public String getUsername() { return userInfo.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; } }

service

 @Service public class UserInfoServiceImpl implements  UserInfoService, UserDetailsService { @Autowired UserInfoMapper userInfoMapper; @Autowired PermissionService permissionService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { QueryWrapper queryWrapper = new QueryWrapper(); queryWrapper.eq("login_name", username); UserInfo userInfo = userInfoMapper.selectOne(queryWrapper); List rights = permissionService.listPermissions(username); UserSecurity userSecurity = new UserSecurity(userInfo); userSecurity.setPermissionValues(rights); return userSecurity; } }

配置类

 @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserInfoServiceImpl userInfoService; @Autowired JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter; @Autowired TokenLoginFilter tokenLoginFilter; @Autowired JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Autowired public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userInfoService).passwordEncoder(passwordEncoderBean()); } @Override protected void configure(HttpSecurity http) thr<div style="color:transparent">来源gaodai.ma#com搞##代!^码@网</div>ows Exception { http.exceptionHandling() .authenticationEntryPoint(jwtAuthenticationEntryPoint) .and().csrf().disable() .authorizeRequests() .antMatchers("/login").permitAll() .antMatchers("/hello").permitAll() .antMatchers("/swagger-ui.html").permitAll() .antMatchers("/webjars/**").permitAll() .antMatchers("/swagger-resources/**").permitAll() .antMatchers("/v2/*").permitAll() .antMatchers("/csrf").permitAll() .antMatchers("/doc.html").permitAll() .antMatchers(HttpMethod.OPTIONS, "/**").anonymous() .anyRequest().authenticated() .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .addFilterAt(tokenLoginFilter, UsernamePasswordAuthenticationFilter.class) .addFilterAfter(jwtAuthorizationTokenFilter, TokenLoginFilter.class).httpBasic() ; } @Bean public PasswordEncoder passwordEncoderBean() { return new BCryptPasswordEncoder(); } @Bean @Override protected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } }

过滤器

一个负责登录

一个负责鉴权

 @Component public class JwtAuthorizationTokenFilter extends OncePerRequestFilter { @Autowired PermissionService permissionService; @Autowired UserInfoServiceImpl userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { //获取当前认证成功用户权限信息 UsernamePasswordAuthenticationToken authRequest = getAuthentication(request); //判断如果有权限信息,放到权限上下文中 if (authRequest != null) { SecurityContextHolder.getContext().setAuthentication(authRequest); } chain.doFilter(request, response); } private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { //从header获取token String token = request.getHeader(Constants.Jwt.TOKEN_HEAD); if (token != null) { //从token获取用户名 String loginName = JwtTokenUtils.getUserName(token); //获取权限列表 UserInfo userInfo = userDetailsService.selectByLoginName(loginName); List permissions = permissionService.listPermissions(loginName); Collection authority = new ArrayList(); for (String permissionValue : permissions) { SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue); authority.add(auth); } return new UsernamePasswordAuthenticationToken(userInfo, token, authority); } return null; } }
 Component public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter { public TokenLoginFilter() { this.setPostOnly(false); this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login","POST")); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //获取表单提交数据 try { UserInfo user = new ObjectMapper().readValue(request.getInputStream(), UserInfo.class); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getLoginName(), user.getPassword(),null); return super.getAuthenticationManager().authenticate(authenticationToken); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(); } } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { UserSecurity userSecurity = (UserSecurity) authResult.getPrincipal(); String token = JwtTokenUtils.createToken(userSecurity.getUsername()); ResponseUtils.out(response, R.ok(token)); } @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { ResponseUtils.out(response, R.fail(ServiceError.LOGIN_FAIL)); } @Autowired @Override public void setAuthenticationManager(AuthenticationManager authenticationManager) { super.setAuthenticationManager(authenticationManager); } }

异常处理

登陆失败异常处理,自定义返回类型

 @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { ResponseUtils.out(response, R.fail(ServiceError.AUTHENTICATION_FAIL)); } }

至于权限和角色,得看具体的业务,我这边就不贴出来了。

流程分析

总体分析

代码肯定不是凭空出来的,必定有章可循。

首先看网上找的这个图:

最前面两个过滤器:密码检验、权限认证。(顺序很重要,鉴权之前总得给人家输入账号密码的机会吧)

想一下传统的session会话模式,登录成功后,服务器维护了session,浏览器得cookie里存放了sessionId,用户访问的时候浏览器自动带上sessionId。

放在以前,服务器端的工作都是是框架帮我们做了(原生session来自于tomcat)。

换成jwt后,需要前端自己去存储token,后台自己来解析token。

但是流程还是一样的,用户登录、获取token、携带token、校验token。。。。。。

登录校验过滤器分析

分析完流程后,就需要看看,如果不用jwt,框架自身是如何实现的。

找到UsernamePasswordAuthenticationFilter这个类

可以发现这个类主要是用来检验密码生成,凭证的。那我们只要继承这个UsernamePasswordAuthenticationFilter类,重写attemptAuthentication,就可以实现登录校验功能。

所以我们的代码是这样的:

权限认证过滤器分析

需要看一下BasicAuthenticationFilter的源码

其中的关键方法:

思考一下,我们如果需要用户信息,会从哪里取?

自然是请求头,前端访问的时候,会把token放在请求头。

取得token后,就要开始解析token了。token正确,将认证信息以及权限信息注入,token不正确则可以抛出异常。这就是我们第二个过滤器的由来

一个地方需要注意一下:

这边的userInfo,就是对应的principal

SecurityContextHolder.getContext().getAuthentication().getPrincipal();

比如这么用:

 @Component public class SecurityUtils { /** * 获取当前用户信息 * @return 用户信息 */ public UserInfo getCurrentUser() { return (UserInfo) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); } }

这里的UserInfo对象,就是principal。

如果你上面放的不对象,而是用户名或者id,那么获取principal的时候,自然是对应的用户名或者id了。

种瓜得瓜,种豆得豆。

配置文件

截取核心片段加点注释。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持gaodaima搞代码网

以上就是SpringSecurity整合jwt权限认证的全流程讲解的详细内容,更多请关注gaodaima搞代码网其它相关文章!


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:SpringSecurity整合jwt权限认证的全流程讲解

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址