SpringBoot + SpringSecurity + JWT认证

  • jwt

    • 简介

      JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准( RFC 7519 ),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。

    • 特点

      • 简洁(Compact)
        可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快。

      • 自包含(Self-contained)
        负载中包含了所有用户所需要的信息,避免了多次查询数据库。

    • java平台的使用

      • maven 支持

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>
        
      • 生成 token

        import io.jsonwebtoken.Jwts;
        import io.jsonwebtoken.SignatureAlgorithm;
        import io.jsonwebtoken.impl.crypto.MacProvider;
        import java.security.Key;
        
        // We need a signing key, so we'll create one just for this example. Usually
        // the key would be read from your application configuration instead.
        Key key = MacProvider.generateKey();
        
        String compactJws = Jwts.builder()
          .setSubject("Joe")
          .signWith(SignatureAlgorithm.HS512, key)
          .compact();
        
      • 解析token

        String compactJws = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJKb2UifQ.yiV1GWDrQyCeoOswYTf_xvlgsnaVVYJM0mU6rkmRBf2T1MBl3Xh2kZii0Q9BdX5-G0j25Qv2WF4lA6jPl5GKuA";
        try {
            Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws);
            //OK, we can trust this JWT
        } catch (SignatureException e) {
            //don't trust the JWT!
        }
        
  • spring-security + jwt 实现思路

    • 在spring-security原本的FilterChain中,
      添加 jwt认证用的Filter :JwtAuthenticationTokenFilter。

    • 客户端请求认证流程

    • 具体实现

      • application.properties 配置

            ##============JSON Web Token========================================
        jwt.header=Authorization
        jwt.secret=mySecret
        jwt.expiration=604800            jwt.route.authentication.path=auth
        jwt.route.authentication.refresh=refresh
        jwt.route.authentication.register="auth/register"
        
      • 添加 JwtAuthenticationTokenFilter

      @Component
      public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
      
          private final Log logger = LogFactory.getLog(this.getClass());
      
          @Autowired
          private UserDetailsService userDetailsService;
      
          @Autowired
          private JwtTokenUtil jwtTokenUtil;
      
          @Value("${jwt.header}")
          private String tokenHeader;
      
          @Override
          protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
              System.out.println("进来了 JwtAuthenticationTokenFilter");
      
              // 得到 请求头的 认证信息 authToken
              String authToken = request.getHeader(this.tokenHeader);
      
              // 解析 authToken 得到 用户名
              String username = jwtTokenUtil.getUsernameFromToken(authToken);
      
              System.out.println("checking authentication for user " + username);
      
              if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
      
                  // 根据用户名从数据库查找用户信息
                  UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
      
                  // 检验token是否有效,并检验其保存的用户信息是否正确
                  if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                      // token 有效,为该请求装载 用户权限信息
                      UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                      authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                      logger.info("authenticated user " + username + ", setting security context");
                      SecurityContextHolder.getContext().setAuthentication(authentication);
                  }
              }
              System.out.println("出去了 JwtAuthenticationTokenFilter");
              chain.doFilter(request, response);
          }
      }
      
      • 将 JwtAuthenticationTokenFilter 添加到 FilterChain 中
      @EnableWebSecurity
      public class MultiHttpSecurityConfig {
      
          @Configuration
          public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
      
              @Autowired
              private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
      
              // 静态资源访问的 url
              private String[] staticFileUrl = {};
              // 不用认证就可访问的 url
              private String[] permitUrl = {};
      
              @Override
              public void configure(WebSecurity web) throws Exception {
                  web.ignoring().antMatchers(staticFileUrl);
                  web.ignoring().antMatchers(permitUrl);
              }
      
              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  http.csrf().disable();
      
                  // 访问url认证
                  http
                          .authorizeRequests()
                          .antMatchers("/admin/**").hasAuthority(String.valueOf(AuthorityName.ROLE_ADMIN))
                          .anyRequest().authenticated();
                  // 配置登陆信息
                  http
                          .formLogin().loginPage("/login")
                          .defaultSuccessUrl("/goIndex")
                          .permitAll()
                          .and();
                  // 配置退出登陆信息
                  http
                          .logout()
                          .logoutSuccessUrl("/login")
                          .invalidateHttpSession(true)
                          .deleteCookies()
                          .and();
      
                  http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);
      
                  http.httpBasic();
              }
          }
      }
      
      • 提供一个jwt认证服务:提供 jwt token 的 生成和更新 功能
        (其实就是一个controller)
      @RestController
      @RequestMapping("authentication")
      public class AuthenticationRestController {
      
          private final Log logger = LogFactory.getLog(this.getClass());
      
          @Value("${jwt.header}")
          private String tokenHeader;
      
          @Autowired
          private AuthenticationManager authenticationManager;
      
          @Autowired
          private JwtTokenUtil jwtTokenUtil;
      
          @Autowired
          private UserDetailsService userDetailsService;
      
          @RequestMapping(value = "${jwt.route.authentication.path}", method = RequestMethod.POST)
          public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtAuthenticationRequest authenticationRequest, Device device) throws AuthenticationException {
              System.out.println("进来了 createAuthenticationToken ");
      
              System.out.println("authenticationRequest : " + authenticationRequest.getPassword() + "::" + authenticationRequest.getUsername());
      
              // Perform the security
              final Authentication authentication = authenticationManager.authenticate(
                      new UsernamePasswordAuthenticationToken(
                              authenticationRequest.getUsername(),
                              authenticationRequest.getPassword()
                      )
              );
              SecurityContextHolder.getContext().setAuthentication(authentication);
      
              // Reload password post-security so we can generate token
              final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
              final String token = jwtTokenUtil.generateToken(userDetails, device);
      
              // Return the token
              return ResponseEntity.ok(new JwtAuthenticationResponse(token));
          }
      }
      
    • 至此,spring-security 整合 jwt 认证 就已经完成了。

参考文档

Spring Security Reference

jjwt

使用JWT和Spring Security保护REST API

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

推荐阅读更多精彩内容