spring security 使用JWT进行权限认证

spring security 使用JWT进行权限认证

pom

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
 </dependency>
 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 ​
 <dependency>
  <groupId>javax.xml.bind</groupId>
  <artifactId>jaxb-api</artifactId>
 </dependency>
 ​
 <dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.9.0</version>
  <scope>compile</scope>
 </dependency>
 ​
 <dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.68</version>
 </dependency></pre>

配置类

 /**
  * @author spp
  * @date 2020-06-11 14:17
  **/
 @EnableWebSecurity
 public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  public void configure(WebSecurity web) throws Exception {
  //忽略请求,不经过security过滤器链
  web.ignoring().mvcMatchers(HttpMethod.GET,"/**");
  }
 ​
  /**
  * 从容器中取出 AuthenticationManagerBuilder,执行方法里面的逻辑之后,放回容器
  * @param authenticationManagerBuilder x
  * @throws Exception
  */
  @Autowired
  public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
  authenticationManagerBuilder.userDetailsService(userDetails).passwordEncoder(new BCryptPasswordEncoder());
  }
 ​
  @Override
  public void configure(HttpSecurity http) throws Exception {
  //解决跨域问题。cors 预检请求放行,让Spring security 放行所有preflight request(cors 预检请求)
  http.authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll();
  //让Security永远不会创建HttpSession,它不会使用HttpSession来获取SecurityContext
  http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  .and().headers().cacheControl();
 ​
  http.authorizeRequests()
  .antMatchers("/admin/**")
  .hasAuthority("root")
  .antMatchers("/").permitAll();
 ​
  //token权限解析认证
  http.addFilterBefore(authJwtFilter,UsernamePasswordAuthenticationFilter.class);
 ​
  //登陆
  http.addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
  //处理异常情况:认证失败和权限不足
  http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
  response.setContentType("application/json;charset=utf8");
  Object error = request.getAttribute("error");
  if (ObjectUtils.isEmpty(error)){
  response.getWriter().println("权限不足");
  return;
  }
  response.getWriter().println(error);
  }).accessDeniedHandler((request, response, accessDeniedException) -> {
  response.setContentType("application/json;charset=utf8");
  response.getWriter().println("你的权限不足以访问该资源");
  });
  }
 ​
  /**
  * 登陆拦截器
  * @return
  * @throws Exception
  */
  @Bean
  public UsernamePassAuthFilter myUsernamePasswordAuthenticationFilter() throws Exception {
  UsernamePassAuthFilter filter = new UsernamePassAuthFilter();
  //成功后处理
  filter.setAuthenticationSuccessHandler(authSuccessHandler);
  //失败后处理
  filter.setAuthenticationFailureHandler(authFailHandler);
  filter.setAuthenticationManager(authenticationManagerBean());
  return filter;
  }
 ​
 }

最重要的http.addFilterBefore(authJwtFilter,UsernamePasswordAuthenticationFilter.class);

JWT过滤器

 /**
  * @author spp
  * @date 2020-06-11 16:21
  * jwt token 认证解析拦截器
  **/
 @Component
 public class AuthJwtFilter extends OncePerRequestFilter {
  public final  String HEADER = "Authorization";
 ​
  /**
  * @param request rq
  * @param response rs
  * @param filterChain 链
  */
  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  String token = request.getHeader(HEADER);
  if (!ObjectUtils.isEmpty(token)){
  logger.info("token-->"+token);
  //是否过期
  boolean check = false;
  try {
  check = JwtTokenUtil.isTokenExpired(token);
  } catch (Exception e) {
  request.setAttribute("error",e.getMessage());
  }
  if (!check){
  String username = JwtTokenUtil.getUsernameFromToken(token);
  if (username != null){
  //通过用户信息得到UserDetails
  List<SimpleGrantedAuthority> list = new ArrayList<>();
  list.add(new SimpleGrantedAuthority("root"));
  AuthUser authUser = new AuthUser(username,null,list);
  //将用户信息存入 authentication,方便后续校验
  UsernamePasswordAuthenticationToken authentication =
  new UsernamePasswordAuthenticationToken(
  authUser.getUsername(),
  null,
  authUser.getAuthorities()
  );
  authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  // 将 authentication 存入 ThreadLocal,方便后续获取用户信息
  SecurityContextHolder.getContext().setAuthentication(authentication);
  }
  }
 ​
  }
  filterChain.doFilter(request, response);
  }
 }

JWT工具类

 /**
  * JWT生成令牌、验证令牌、获取令牌
  */
 @Component
 @NoArgsConstructor
 public class JwtTokenUtil {
  /**
  * 私钥
  */
 ​
  private static final String SECRET_KEY = "auth_sp";
 ​
  /**
  * 过期时间 毫秒,设置默认1周的时间过期
  */
  private static final long EXPIRATION_TIME = 3600000L * 2;
 ​
  /**
  * 生成令牌
  * @param userDetails 用户
  * @return 令牌
  */
  public static String generateToken(UserDetails userDetails) {
  Map<String, Object> claims = new HashMap<>(2);
  claims.put(Claims.SUBJECT, userDetails.getUsername());
  claims.put(Claims.ISSUED_AT, new Date());
  return generateToken(claims);
  }
 ​
  /**
  * 从令牌中获取用户名
  * @param token 令牌
  * @return 用户名
  */
  public static String getUsernameFromToken(String token) {
  String username = null;
  try {
  Claims claims = getClaimsFromToken(token);
  username = claims.getSubject();
  } catch (Exception e) {
  System.out.println("e = " + e.getMessage());
  }
  return username;
  }
 ​
  /**
  * 判断令牌是否过期
  *
  * @param token 令牌
  * @return 是否过期
  */
  public static Boolean isTokenExpired(String token) throws  Exception{
  try {
  Claims claims = getClaimsFromToken(token);
  Date expiration = claims.getExpiration();
  return expiration.before(new Date());
  } catch (Exception e) {
  throw new Exception("签名过期");
  }
  }
 ​
  /**
  * 刷新令牌
  *
  * @param token 原令牌
  * @return 新令牌
  */
  public static String refreshToken(String token) {
  String refreshedToken;
  try {
  Claims claims = getClaimsFromToken(token);
  claims.put(Claims.ISSUED_AT, new Date());
  refreshedToken = generateToken(claims);
  } catch (Exception e) {
  refreshedToken = null;
  }
  return refreshedToken;
  }
 ​
  /**
  * 验证令牌
  *
  * @param token       令牌
  * @param userDetails 用户
  * @return 是否有效
  */
  public static Boolean validateToken(String token, UserDetails userDetails) throws Exception {
  AuthUser user = (AuthUser) userDetails;
  String username = getUsernameFromToken(token);
  return (username.equals(user.getUsername()) && !isTokenExpired(token));
  }
 ​
  /**
  * 从数据声明生成令牌
  *
  * @param claims 数据声明
  * @return 令牌
  */
  private static String generateToken(Map<String, Object> claims) {
  Date expirationDate = new Date(System.currentTimeMillis()+ EXPIRATION_TIME);
  HashMap<String, Object> map = new HashMap<>(1);map.put("typ",Header.JWT_TYPE);
  return Jwts.builder().setHeader(map).setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, SECRET_KEY).compact();
  }
 ​
  /**
  * 从令牌中获取数据声明
  *
  * @param token 令牌
  * @return 数据声明
  */
  private static Claims getClaimsFromToken(String token) throws Exception {
  Claims claims = null;
  try {
  claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
  } catch (Exception e) {
  new Throwable(e);
  }
  return claims;
  }
 }

测试

不携带token

携带正确的token

故意将token破坏

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353