• Spring Boot整合JWT实现用户认证


    初探JWT

    什么是JWT

    JWT(Json Web Token),是一种工具,格式为XXXX.XXXX.XXXX的字符串,JWT以一种安全的方式在用户和服务器之间传递存放在JWT中的不敏感信息。

    为什么要用JWT

    设想这样一个场景,在我们登录一个网站之后,再把网页或者浏览器关闭,下一次打开网页的时候可能显示的还是登录的状态,不需要再次进行登录操作,通过JWT就可以实现这样一个用户认证的功能。当然使用Session可以实现这个功能,但是使用Session的同时也会增加服务器的存储压力,而JWT是将存储的压力分布到各个客户端机器上,从而减轻服务器的压力。

    JWT长什么样

    JWT由3个子字符串组成,分别为HeaderPayload以及Signature,结合JWT的格式即:Header.Payload.Signature。(Claim是描述Json的信息的一个Json,将Claim转码之后生成Payload)。

    Header

    Header是由以下这个格式的Json通过Base64编码(编码不是加密,是可以通过反编码的方式获取到这个原来的Json,所以JWT中存放的一般是不敏感的信息)生成的字符串,Header中存放的内容是说明编码对象是一个JWT以及使用“SHA-256”的算法进行加密(加密用于生成Signature)

    1. "typ":"JWT"
    2. "alg":"HS256" 

    Claim

    Claim是一个Json,Claim中存放的内容是JWT自身的标准属性,所有的标准属性都是可选的,可以自行添加,比如:JWT的签发者、JWT的接收者、JWT的持续时间等;同时Claim中也可以存放一些自定义的属性,这个自定义的属性就是在用户认证中用于标明用户身份的一个属性,比如用户存放在数据库中的id,为了安全起见,一般不会将用户名及密码这类敏感的信息存放在Claim中。将Claim通过Base64转码之后生成的一串字符串称作Payload。

    1. "iss":"Issuer —— 用于说明该JWT是由谁签发的"
    2. "sub":"Subject —— 用于说明该JWT面向的对象"
    3. "aud":"Audience —— 用于说明该JWT发送给的用户"
    4. "exp":"Expiration Time —— 数字类型,说明该JWT过期的时间"
    5. "nbf":"Not Before —— 数字类型,说明在该时间之前JWT不能被接受与处理"
    6. "iat":"Issued At —— 数字类型,说明该JWT何时被签发"
    7. "jti":"JWT ID —— 说明标明JWT的唯一ID"
    8. "user-definde1":"自定义属性举例"
    9. "user-definde2":"自定义属性举例" 

    Signature

    Signature是由Header和Payload组合而成,将Header和Claim这两个Json分别使用Base64方式进行编码,生成字符串Header和Payload,然后将Header和Payload以Header.Payload的格式组合在一起形成一个字符串,然后使用上面定义好的加密算法和一个密匙(这个密匙存放在服务器上,用于进行验证)对这个字符串进行加密,形成一个新的字符串,这个字符串就是Signature。

    总结

    JWT实现认证的原理

    服务器在生成一个JWT之后会将这个JWT会以Authorization : Bearer JWT 键值对的形式存放在cookies里面发送到客户端机器,在客户端再次访问收到JWT保护的资源URL链接的时候,服务器会获取到cookies中存放的JWT信息,首先将Header进行反编码获取到加密的算法,在通过存放在服务器上的密匙对Header.Payload 这个字符串进行加密,比对JWT中的Signature和实际加密出来的结果是否一致,如果一致那么说明该JWT是合法有效的,认证成功,否则认证失败。

    JWT实现用户认证的流程图

    JWT的代码实现

    这里的代码实现使用的是Spring Boot(版本号:1.5.10)框架,以及Apache Ignite(版本号:2.3.0)数据库。有关Ignite和Spring Boot的整合可以查看这里。

    http://blog.csdn.net/ltl112358/article/details/79399026

    代码说明:

    代码中与JWT有关的内容如下:

    • config包中JwtCfg类配置生成一个JWT并配置了JWT拦截的URL

    • controller包中PersonController 用于处理用户的登录注册时生成JWT,SecureController 用于测试JWT

    • model包中JwtFilter 用于处理与验证JWT的正确性

    • 其余属于Ignite数据库访问的相关内容

    JwtCfg 类

    这个类中声明了一个@Bean ,用于生成一个过滤器类,对/secure 链接下的所有资源访问进行JWT的验证

    1. /**
    2.  * This is Jwt configuration which set the url "/secure/*" for filtering
    3.  * @program: users
    4.  * @create: 2018-03-03 21:18
    5.  **/
    6. @Configuration
    7. public class JwtCfg {
    8.     @Bean
    9.     public FilterRegistrationBean jwtFilter() {
    10.         final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    11.         registrationBean.setFilter(new JwtFilter());
    12.         registrationBean.addUrlPatterns("/secure/*");
    13.         return registrationBean;
    14.     }
    15. }

    JwtFilter 类

    这个类声明了一个JWT过滤器类,从Http请求中提取JWT的信息,并使用了”secretkey”这个密匙对JWT进行验证

    1. /**
    2.  * Check the jwt token from front end if is invalid
    3.  * @program: users
    4.  * @create: 2018-03-01 11:03
    5.  **/
    6. public class JwtFilter extends GenericFilterBean {
    7.     public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
    8.             throws IOException, ServletException {
    9.         // Change the req and res to HttpServletRequest and HttpServletResponse
    10.         final HttpServletRequest request = (HttpServletRequest) req;
    11.         final HttpServletResponse response = (HttpServletResponse) res;
    12.         // Get authorization from Http request
    13.         final String authHeader = request.getHeader("authorization");
    14.         // If the Http request is OPTIONS then just return the status code 200
    15.         // which is HttpServletResponse.SC_OK in this code
    16.         if ("OPTIONS".equals(request.getMethod())) {
    17.             response.setStatus(HttpServletResponse.SC_OK);
    18.             chain.doFilter(req, res);
    19.         }
    20.         // Except OPTIONSother request should be checked by JWT
    21.         else {
    22.             // Check the authorization, check if the token is started by "Bearer "
    23.             if (authHeader == null || !authHeader.startsWith("Bearer ")) {
    24.                 throw new ServletException("Missing or invalid Authorization header");
    25.             }
    26.             // Then get the JWT token from authorization
    27.             final String token = authHeader.substring(7);
    28.             try {
    29.                 // Use JWT parser to check if the signature is valid with the Key "secretkey"
    30.                 final Claims claims = Jwts.parser().setSigningKey("secretkey").parseClaimsJws(token).getBody();
    31.                 // Add the claim to request header
    32.                 request.setAttribute("claims", claims);
    33.             } catch (final SignatureException e) {
    34.                 throw new ServletException("Invalid token");
    35.             }
    36.             chain.doFilter(req, res);
    37.         }
    38.     }
    39. }

    PersonController 类

    这个类中在用户进行登录操作成功之后,将生成一个JWT作为返回

    1. /**
    2.  * @program: users
    3.  * @create: 2018-02-27 19:28
    4.  **/
    5. @RestController
    6. public class PersonController {
    7.     @Autowired
    8.     private PersonService personService;
    9.     /**
    10.      * User register with whose username and password
    11.      * @param reqPerson
    12.      * @return Success message
    13.      * @throws ServletException
    14.      */
    15.     @RequestMapping(value = "/register"method = RequestMethod.POST)
    16.     public String register(@RequestBody() ReqPerson reqPerson) throws ServletException {
    17.         // Check if username and password is null
    18.         if (reqPerson.getUsername() == "" || reqPerson.getUsername() == null
    19.                 || reqPerson.getPassword() == "" || reqPerson.getPassword() == null)
    20.             throw new ServletException("Username or Password invalid!");
    21.         // Check if the username is used
    22.         if(personService.findPersonByUsername(reqPerson.getUsername()) != null)
    23.             throw new ServletException("Username is used!");
    24.         // Give a default role : MEMBER
    25.         List<Role> roles = new ArrayList<Role>();
    26.         roles.add(Role.MEMBER);
    27.         // Create a person in ignite
    28.         personService.save(new Person(reqPerson.getUsername(), reqPerson.getPassword(), roles));
    29.         return "Register Success!";
    30.     }
    31.     /**
    32.      * Check user`s login info, then create a jwt token returned to front end
    33.      * @param reqPerson
    34.      * @return jwt token
    35.      * @throws ServletException
    36.      */
    37.     @PostMapping
    38.     public String login(@RequestBody() ReqPerson reqPerson) throws ServletException {
    39.         // Check if username and password is null
    40.         if (reqPerson.getUsername() == "" || reqPerson.getUsername() == null
    41.                 || reqPerson.getPassword() == "" || reqPerson.getPassword() == null)
    42.             throw new ServletException("Please fill in username and password");
    43.         // Check if the username is used
    44.         if(personService.findPersonByUsername(reqPerson.getUsername()) == null
    45.                 || !reqPerson.getPassword().equals(personService.findPersonByUsername(reqPerson.getUsername()).getPassword())){
    46.             throw new ServletException("Please fill in username and password");
    47.         }
    48.         // Create Twt token
    49.         String jwtToken = Jwts.builder().setSubject(reqPerson.getUsername()).claim("roles""member").setIssuedAt(new Date())
    50.                 .signWith(SignatureAlgorithm.HS256"secretkey").compact();
    51.         return jwtToken;
    52.     }
    53. }

    SecureController 类

    这个类中只是用于测试JWT功能,当用户认证成功之后,/secure 下的资源才可以被访问

    1. /**
    2.  * Test the jwt, if the token is valid then return "Login Successful"
    3.  * If is not valid, the request will be intercepted by JwtFilter
    4.  * @program: users
    5.  * @create: 2018-03-01 11:05
    6.  **/
    7. @RestController
    8. @RequestMapping("/secure")
    9. public class SecureController {
    10.     @RequestMapping("/users/user")
    11.     public String loginSuccess() {
    12.         return "Login Successful!";
    13.     }
    14. }

    代码功能测试

    本例使用Postman对代码进行测试,这里并没有考虑到安全性传递的明文密码,实际上应该用SSL进行加密

    1.首先进行一个新的测试用户的注册,可以看到注册成功的提示返回

    2.再让该用户进行登录,可以看到登录成功之后返回的JWT字符串

    3.直接申请访问/secure/users/user ,这时候肯定是无法访问的,服务器返回500错误

    4.将获取到的JWT作为Authorization属性提交,申请访问/secure/users/user ,可以访问成功

    示例代码

    https://github.com/ltlayx/SpringBoot-Ignite

  • 相关阅读:
    [CISCN2019 总决赛 Day1 Web4]Laravel1
    Linux命令(107)之basename
    点云从入门到精通技术详解100篇-基于激光点云的道路目标检测(续)
    2011年03月16日 Go生态洞察:Go朝着更高稳定性迈进
    MYSQL的系统数据表空间,用户数据表空间,系统临时表空间,用户临时表空间详解
    HTML5播放 M3U8的hls流地址
    Django 全局配置 settings 详解
    放弃华为天才少年 Offer、和陆奇 10 分钟定投资,25 岁的关超宇想让钢铁侠成为现实 | AGI 技术 50 人...
    Zeno节点系统中的C++最佳实践
    海外媒体软文发稿:南非新闻通稿宣发,谷歌新闻收录
  • 原文地址:https://blog.csdn.net/z_ssyy/article/details/128195504