SpringSecurity - 整合JWT使用 Token 认证授权
一、SpringSecurity
前面讲解了SpringSecurity的动态认证和动态权限角色,我们都知道在现在大多都是微服务前后端分离的模式开发,前面讲的还是基于Session的,本篇我们整合JWT实现使用Token认证授权。
上篇文章地址:https://blog.csdn.net/qq_43692950/article/details/122394611
在开始前需要了解JWT,如果不了解,可以先看下下面这篇我的博客:
https://blog.csdn.net/qq_43692950/article/details/107443397
二、SpringSecurity 整合JWT使用 Token 认证授权
本篇文章还是接着上篇文章进行讲解,数据库还是使用上两篇文章中创建的数据库:
由于我们要使用JWT生成Token和存储一些信息,所以先引入JWT的依赖:
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.6.0</version>
</dependency>
编写JwtTool
工具:
@Data
@Component
public class JwtTool {private String key = "com.bxc";private long overtime = 1000 * 60 * 60;public String CreateToken(String userid, String username, List<String> roles) {long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);JwtBuilder builder = Jwts.builder().setId(userid).setSubject(username).setIssuedAt(now).signWith(SignatureAlgorithm.HS256, key).claim("roles", roles);if (overtime > 0) {builder.setExpiration(new Date(nowMillis + overtime));}return builder.compact();}public boolean VerityToken(String token) {try {Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();if (claims != null) {return true;}} catch (Exception e) {e.printStackTrace();}return false;}public String getUserid(String token) {try {Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();if (claims != null) {return claims.getId();}} catch (Exception e) {e.printStackTrace();}return null;}public String getUserName(String token) {try {Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();if (claims != null) {return claims.getSubject();}} catch (Exception e) {e.printStackTrace();}return null;}public List<String> getUserRoles(String token) {try {Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();if (claims != null) {return (List<String>) claims.get("roles");}} catch (Exception e) {e.printStackTrace();}return null;}public String getClaims(String token, String param) {try {Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();if (claims != null) {return claims.get(param).toString();}} catch (Exception e) {e.printStackTrace();}return null;}
}
使用CreateToken
就可以生成下面这种字符串:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4Iiwic3ViIjoiYWRtaW4iLCJpYXQiOjE2NDE3Mjg5NzgsInJvbGVzIjpbIlJPTEVfYWRtaW4iXSwiZXhwIjoxNjQxNzMyNTc4fQ.qI_pXiwm2IzZNRdyKvTRuSj0JxiPHepPOXg_u6AAE88
我们就使用类似上面这串做我们的Token。
既然使用Token了,肯定大多都是采用前后端分离架构,一般都是采用JSON进行交互的,但细心的会发现,前面的登录都是一个form表单的形式,所以第一步我们先把登录换成JSON的形式。
下面就需要重写自己的登录过滤器,需要实现UsernamePasswordAuthenticationFilter
接口,其中attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
表示获取用户用户名密码的入口,我们可以在这里自定义接受用户名密码,比如以json形式接受,然后再传递给Security
,然后Security
下面就会去调用UserDetailsService
做用户名和密码的正确性验证,如果用户名密码正确那就是登录成功,就会触发该实现下的successfulAuthentication
方法,否则就是unsuccessfulAuthentication
方法,我们可以在相应的方法中编写相应的提示返回给客户端:
public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {private JwtTool jwtTool;public LoginAuthenticationFilter(AuthenticationManager authenticationManager, JwtTool jwtTool) {this.setAuthenticationManager(authenticationManager);this.jwtTool = jwtTool;
// this.setPostOnly(false);
// this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login","POST"));}@SneakyThrows@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {response.setContentType("text/json;charset=utf-8");if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {BufferedReader br = null;try {br = new BufferedReader(new InputStreamReader(request.getInputStream(), "utf-8"));String line = null;StringBuilder sb = new StringBuilder();while ((line = br.readLine()) != null) {sb.append(line);}JSONObject json = JSONObject.parseObject(sb.toString());System.out.println(json.toString());String username = json.getString("username");String password = json.getString("password");UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);this.setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);} catch (IOException e) {e.printStackTrace();response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(400).message("参数错误!").build()));}} else {return super.attemptAuthentication(request, response);}response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(400).message("参数错误!").build()));return null;}@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,FilterChain chain, Authentication authentication) throws IOException, ServletException {UserEntity user = (UserEntity) authentication.getPrincipal();String username = user.getUsername();List<GrantedAuthority> authorities = (List<GrantedAuthority>) user.getAuthorities();List<String> roles = authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());String token = jwtTool.CreateToken(String.valueOf(user.getId()), username, roles);response.setContentType("text/json;charset=utf-8");Map<String, Object> map = new HashMap<>();map.put("username", username);map.put("role", roles);map.put("token", token);response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().data(map).build()));}@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {response.setContentType("text/json;charset=utf-8");if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(400).message("用户名或密码错误!").build()));} else if (e instanceof DisabledException) {response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(400).message("账户被禁用,请联系管理员!").build()));} else {response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(400).message("用户名或密码错误!").build()));}}}
登录成功后,我们使用Jwt生成了一串Token并返还给用户,以后的所有请求都需要携带该Token,但Security
默认的是从Session中获取用户信息,显然也不符合我们的要求,所以下面我们要重写自己的Token过滤器。
需要实现BasicAuthenticationFilter
接口,我们只需在doFilterInternal
中做自己的逻辑即可,如果全部OK就放行该过滤器即可:
@Slf4j
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {private JwtTool jwtTool;public JWTAuthenticationFilter(AuthenticationManager authenticationManager, JwtTool jwtTool) {super(authenticationManager);this.jwtTool = jwtTool;}public JWTAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {super(authenticationManager, authenticationEntryPoint);}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {response.setContentType("text/json;charset=utf-8");String token = request.getHeader("token");if (StringUtils.isEmpty(token)){response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(401).message("登录失效!").build()));return;}boolean isold = jwtTool.VerityToken(token);if (!isold) {response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(401).message("登录失效!").build()));return;}String username = jwtTool.getUserName(token);if (StringUtils.isEmpty(username)) {response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(401).message("登录失效!").build()));return;}List<String> roles = jwtTool.getUserRoles(token);if (roles.isEmpty()) {response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(403).message("权限不足!").build()));return;}List<GrantedAuthority> authorities = roles.stream().map(r -> new SimpleGrantedAuthority(r)).collect(Collectors.toList());UserEntity principal = new UserEntity();principal.setUsername(username);try {UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(principal, null, authorities);SecurityContextHolder.getContext().setAuthentication(authentication);String userid = jwtTool.getUserid(token);request.setAttribute("userid", userid);request.setAttribute("username", username);
// request.setAttribute("role", role);chain.doFilter(request, response);} catch (Exception e) {e.printStackTrace();response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(401).message("登录失效!").build()));}}
}
上面我们重写了接受用户名密码,和校验Token的过滤器,显然已经符合我们前后端分离架构,但是还有一个就是无权限的返回,在上两篇就可以看出,无权限是返回的403错误,显然也不符合,应该要修改为JSON的返回。
我们可以实现AccessDeniedHandler
这个接口,来做无权限自定义的返回:
@Component
public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)throws IOException, ServletException {response.setContentType("text/json;charset=utf-8");response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(403).message("权限不足!").build()));}}
最后修改WebSecurityConfig
,将上面的过滤器添加到Security
中,替换到默认的过滤器:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredUserService userService;@AutowiredCustomAccessDecisionManager customAccessDecisionManager;@AutowiredCustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;@AutowiredAuthenticationAccessDeniedHandler authenticationAccessDeniedHandler;@AutowiredJwtTool jwtTool;@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(password());}@BeanPasswordEncoder password() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O o) {o.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);o.setAccessDecisionManager(customAccessDecisionManager);return o;}}).antMatchers("/**").fullyAuthenticated().and().formLogin().loginProcessingUrl("/login").permitAll().and().exceptionHandling().accessDeniedHandler(authenticationAccessDeniedHandler).and().addFilter(new JWTAuthenticationFilter(authenticationManager(), jwtTool)).addFilter(new LoginAuthenticationFilter(authenticationManager(),jwtTool)).csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/register/**");}@BeanRoleHierarchy roleHierarchy() {RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();String hierarchy = "ROLE_admin > ROLE_user > ROLE_common";roleHierarchy.setHierarchy(hierarchy);return roleHierarchy;}
}
相比于上一次的修改,这里就是通过addFilter
的方式添加我们的过滤器。
下面就可以启动项目,访问http://localhost:8080/admin/test
测试接口:
直接就是返回登录失效了,下面我们使用PostMan
登录:http://localhost:8080/login
可以看到这里我把权限也返回出来进行测试,表示该用户只能访问admin/**
,下面我们使用Token访问http://localhost:8080/admin/test
如果访问common/**
:
到这里就实现了使用Jwt Token的认证授权了。
喜欢的小伙伴可以关注我的个人微信公众号,获取更多学习资料!
SpringSecurity - 整合JWT使用 Token 认证授权相关推荐
- SpringBoot 整合 JWT 实现 Token 验证
前言 在Spring Security整合oauth2实现认证token也不满足实际生产需求的时候,可以整合Jwt实现token认证,完全手写获取token,认证token的方法. Maven依赖包 ...
- Spring Security Oauth2 JWT 实现用户认证授权功能
Spring Security Oauth2 JWT 一 用户认证授权 1. 需求分析 1.1 用户认证与授权 什么是用户身份认证? 用户身份认证即用户去访问系统资源时系统要求验证用户的身份信息,身份 ...
- SpringSecurity权限管理系统实战—六、SpringSecurity整合JWT
文章目录 系列目录 前言 一.无状态登录 二.JWT介绍 1.什么是jwt 头部(Header) 载荷(Payload) 签名(Signature) 2.JWT工作流程 3.简单实现 三.整合JWT ...
- jwt重放攻击_【干货分享】基于JWT的Token认证机制及安全问题
一步一步教你基于JWT的Token认证机制实现,以及如何防范XSS攻击.Replay攻击和中间人攻击. 文章目录 一.几种常用的认证机制 1.1 HTTP Basic Auth HTTP Basic ...
- Spring Boot整合JWT实现用户认证
JWT实现用户认证 在介绍完JWT之后我们使用springboot整合JWT实现用户认证. 前后端分离使用JWT做用户认证(概述) JWT实现认证的原理 服务器在生成一个JWT之后会将这个JWT会以 ...
- 【SpringBoot】44、SpringBoot中整合JWT实现Token验证(整合篇)
什么是JWT? Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准((RFC 7519),该 token 被设计为紧凑且安全的,特别适用于分 ...
- 基于JWT的Token认证机制实现
一.基于JWT的Token认证机制实现 1.什么是JWT JSON Web Token(JWT)是一个非常轻巧的规范.这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息. 2.JWT组成 ...
- springboot+shiro+jwt实现token认证登录
准备: springboot 2.5.5 jdk 1.8 没有操作刷新token功能,也没有放redis做缓存 1.先贴代码 2.后讲一下验证逻辑 1.导入依赖 <!--shiro-->& ...
- springsecurity整合jwt实现授权认证,权限分配
1.前期准备工作 1.1首先需要导入jwt依赖和springsecurity的依赖 <!--security 依赖 --><dependency><groupId> ...
最新文章
- php设计模式中的类型安全 指--只接受特定的对象 ---以避免发生错误
- 用html做一个发送邮件验证,邮件发送还有问题吗?送大家一个写好的类吧,支持stmp认证、HTML格式邮件-PHP教程,PHP应用...
- 9.png(9位图)在android中作为background使用导致居中属性不起作用的解决方法
- java中arges.length_java中的args.length
- 密码学系列之:memory-hard函数
- Bootstrap学习3
- linux 去掉 ^M 的方法
- shell 编程 : 函数
- BertEmbedding的各种用法
- 省赛来了(组合数)nyoj158
- Flash 第十一章 引导层和遮罩层动画
- Linux命令行运行多线程程序 和 QT集成IDE下运行多线程程序的问题。
- 原型链面向对象----多态
- 移动端图形化报表界面设计_移动端报表设计-帆软
- 员工跟母亲吐槽被同事欺负,隔天母亲跑到公司打同事,结果蒙了
- 【编译原理】语言认知之Java、Python、C++快速排序三者运行效率与开发效率比较
- unity 陶瓷质感_一种基于Unity3D的虚拟陶瓷设计方法与流程
- 【mysql】事务的四大特性
- upload单独上传和统一上传
- 使用minio搭建高性能对象存储-第一部分:原型