新知一下
海量新知
5 9 9 1 4 2 7

还在直接用 JWT 做鉴权?JJWT 真香

民工哥技术之路 | 专注互联网IT开源技术分享 2021/10/25 12:53

jwt是什么?

JWTs是JSON对象的编码表示。JSON对象由零或多个名称/值对组成,其中名称为字符串,值为任意JSON值。JWT有助于在clear(例如在URL中)发送这样的信息,可以被信任为不可读(即加密的)、不可修改的(即签名)和URL - safe(即Base64编码的)。

Jwt的组成

  • Header: 标题包含了令牌的元数据,并且在最小包含签名和/或加密算法的类型

  • Claims: Claims包含您想要签署的任何信息

  • JSON Web Signature (JWS): 在header中指定的使用该算法的数字签名和声明

例如:

Header:

{

  "alg""HS256",

  "typ""JWT"

}

Claims:

{

  "sub""1234567890",

  "name""John Doe",

  "admin"true

}

Signature:

base64UrlEncode(Header) + "." + base64UrlEncode(Claims),

加密生成的token:

新知达人, 还在直接用 JWT 做鉴权?JJWT 真香

如何保证 JWT 安全

有很多库可以帮助您创建和验证JWT,但是当使用JWT时,仍然可以做一些事情来限制您的安全风险。在您信任JWT中的任何信息之前,请始终验证签名。这应该是给定的。

换句话说,如果您正在传递一个秘密签名密钥到验证签名的方法,并且签名算法被设置为“none”,那么它应该失败验证。

确保签名的秘密签名,用于计算和验证签名。秘密签名密钥只能由发行者和消费者访问,不能在这两方之外访问。

不要在JWT中包含任何敏感数据。这些令牌通常是用来防止操作(未加密)的,因此索赔中的数据可以很容易地解码和读取。

如果您担心重播攻击,包括一个nonce(jti索赔)、过期时间(exp索赔)和创建时间(iat索赔)。这些在JWT规范中定义得很好。

新知达人, 还在直接用 JWT 做鉴权?JJWT 真香

jwt的框架:JJWT

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

  • JJWT的目标是最容易使用和理解用于在JVM上创建和验证JSON Web令牌(JWTs)的库。

  • JJWT是基于JWT、JWS、JWE、JWK和JWA RFC规范的Java实现。

  • JJWT还添加了一些不属于规范的便利扩展,比如JWT压缩和索赔强制。

规范兼容:

  • 创建和解析明文压缩JWTs

  • 创建、解析和验证所有标准JWS算法的数字签名紧凑JWTs(又称JWSs):

  • HS256: HMAC using SHA-256

  • HS384: HMAC using SHA-384

  • HS512: HMAC using SHA-512

  • RS256: RSASSA-PKCS-v1_5 using SHA-256

  • RS384: RSASSA-PKCS-v1_5 using SHA-384

  • RS512: RSASSA-PKCS-v1_5 using SHA-512

  • PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256

  • PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384

  • PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512

  • ES256: ECDSA using P-256 and SHA-256

  • ES384: ECDSA using P-384 and SHA-384

  • ES512: ECDSA using P-521 and SHA-512

这里以github上的demo演示,理解原理,集成到自己项目中即可。

应用采用 spring boot + angular + jwt

结构图

新知达人, 还在直接用 JWT 做鉴权?JJWT 真香

Maven 引进 : pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nibado.example</groupId>

    <artifactId>jwt-angular-spring</artifactId>

    <version>0.0.2-SNAPSHOT</version>

    <properties>

        <maven.compiler.source>1.8</maven.compiler.source>

        <maven.compiler.target>1.8</maven.compiler.target>

        <commons.io.version>2.4</commons.io.version>

        <jjwt.version>0.6.0</jjwt.version>

        <junit.version>4.12</junit.version>

        <spring.boot.version>1.5.3.RELEASE</spring.boot.version>

    </properties>

    <build>

        <plugins>

            <plugin>

                <groupId>org.springframework.boot</groupId>

                <artifactId>spring-boot-maven-plugin</artifactId>

                <version>${spring.boot.version}</version>

                <executions>

                    <execution>

                        <goals>

                            <goal>repackage</goal>

                        </goals>

                    </execution>

                </executions>

            </plugin>

        </plugins>

    </build>

    <dependencies>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-web</artifactId>

            <version>${spring.boot.version}</version>

        </dependency>

        <dependency>

            <groupId>commons-io</groupId>

            <artifactId>commons-io</artifactId>

            <version>${commons.io.version}</version>

        </dependency>

        <dependency>

            <groupId>io.jsonwebtoken</groupId>

            <artifactId>jjwt</artifactId>

            <version>${jjwt.version}</version>

        </dependency>

        <dependency>

            <groupId>junit</groupId>

            <artifactId>junit</artifactId>

            <version>${junit.version}</version>

        </dependency>

    </dependencies>

</project>

WebApplication.java

package com.nibado.example.jwtangspr;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

import org.springframework.boot.web.servlet.FilterRegistrationBean;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.Configuration;

@EnableAutoConfiguration

@ComponentScan

@Configuration

public class WebApplication {

 

    //过滤器

    @Bean

    public FilterRegistrationBean jwtFilter() {

        final FilterRegistrationBean registrationBean = new FilterRegistrationBean();

        registrationBean.setFilter(new JwtFilter());

        registrationBean.addUrlPatterns("/api/*");

        return registrationBean;

    }

    public static void main(final String[] args) throws Exception {

        SpringApplication.run(WebApplication.classargs);

    }

}

JwtFilter.java

package com.nibado.example.jwtangspr;

import java.io.IOException;

import javax.servlet.FilterChain;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.filter.GenericFilterBean;

import io.jsonwebtoken.Claims;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureException;

public class JwtFilter extends GenericFilterBean {

    @Override

    public void doFilter(final ServletRequest req,

                         final ServletResponse res,

                         final FilterChain chain)
 throws IOException, ServletException 
{

        final HttpServletRequest request = (HttpServletRequest) req;

        //客户端将token封装在请求头中,格式为(Bearer后加空格):Authorization:Bearer +token

        final String authHeader = request.getHeader("Authorization");

        if (authHeader == null || !authHeader.startsWith("Bearer ")) {

            throw new ServletException("Missing or invalid Authorization header.");

        }

        //去除Bearer 后部分

        final String token = authHeader.substring(7);

        try {

         //解密token,拿到里面的对象claims

            final Claims claims = Jwts.parser().setSigningKey("secretkey")

                .parseClaimsJws(token).getBody();

            //将对象传递给下一个请求

            request.setAttribute("claims", claims);

        }

        catch (final SignatureException e) {

            throw new ServletException("Invalid token.");

        }

        chain.doFilter(req, res);

    }

}

UserController.java

package com.nibado.example.jwtangspr;

import java.util.Arrays;

import java.util.Date;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import javax.servlet.ServletException;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureAlgorithm;

@RestController

@RequestMapping("/user")

public class UserController {

 //这里模拟数据库

    private final Map<String, List<String>> userDb = new HashMap<>();

    @SuppressWarnings("unused")

    private static class UserLogin {

        public String name;

        public String password;

    }

    

    public UserController() {

        userDb.put("tom", Arrays.asList("user"));

        userDb.put("wen", Arrays.asList("user""admin"));

    }

    /*以上是模拟数据库,并往数据库插入tom和sally两条记录*/

    

    

    @RequestMapping(value = "login", method = RequestMethod.POST)

    public LoginResponse login(@RequestBody final UserLogin login)

        throws ServletException 
{

        if (login.name == null || !userDb.containsKey(login.name)) {

            throw new ServletException("Invalid login");

        }

        

        //加密生成token

        return new LoginResponse(Jwts.builder().setSubject(login.name)

            .claim("roles", userDb.get(login.name)).setIssuedAt(new Date())

            .signWith(SignatureAlgorithm.HS256, "secretkey").compact());

    }

    @SuppressWarnings("unused")

    private static class LoginResponse {

        public String token;

        public LoginResponse(final String token) {

            this.token = token;

        }

    }

}

ApiController.java

package com.nibado.example.jwtangspr;

import io.jsonwebtoken.Claims;

import java.util.List;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;

@RestController

@RequestMapping("/api")

public class ApiController {

 @SuppressWarnings("unchecked")

 @RequestMapping(value = "role/{role}", method = RequestMethod.GET)

 public Boolean login(@PathVariable final String role,

   final HttpServletRequest request)
 throws ServletException 
{

  final Claims claims = (Claims) request.getAttribute("claims");

  return ((List<String>) claims.get("roles")).contains(role);

 }

}

index.html

<!doctype html>

<html ng-app="myApp">

<head>

    <meta charset="utf-8"/>

    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>

    <title>JSON Web Token / AngularJS / Spring Boot example</title>

    <meta name="description" content="">

    <meta name="viewport" content="width=device-width">

    <link rel="stylesheet" href="libs/bootstrap/css/bootstrap.css">

    <script src="libs/jquery/jquery.js"></script>

    <script src="libs/bootstrap/js/bootstrap.js"></script>

    <script src="libs/angular/angular.js"></script>

    <script src="app.js"></script>

</head>

<body>

<div class="container" ng-controller='MainCtrl'>

 <h1>{{greeting}}</h1>

 <div ng-show="!loggedIn()">

  Please log in (tom and sally are valid names)</br>

  <form ng-submit="login()">

   Username: <input type="text" ng-model="userName"/><span><input type="submit" value="Login"/>

  </form>

 </div>

 <div class="alert alert-danger" role="alert" ng-show="error.data.message">

   <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>

   <span class="sr-only">Error:</span>

   {{error.data.message}}

 </div> 

 <div ng-show="loggedIn()">

  <div class="row">

   <div class="col-md-6">

    <h3><span class="label label-success">Success!</span> Welcome {{userName}}</h3>

     <a href ng-click="logout()">(logout)</a>

   </div>

   <div class="col-md-4">

   <div class="row header">

   <div class="col-sm-4">{{userName}} is a</div>

  </div>

  <div class="row">

   <div class="col-sm-2">User</div>

   <div class="col-sm-2"><span class="glyphicon glyphicon-ok" aria-hidden="true" ng-show="roleUser"></span></div>

  </div>

  <div class="row">

   <div class="col-sm-2">Admin</div>

   <div class="col-sm-2"><span class="glyphicon glyphicon-ok" aria-hidden="true" ng-show="roleAdmin"></span></div>

  </div> 

  <div class="row">

   <div class="col-sm-2">Foo</div>

   <div class="col-sm-2"><span class="glyphicon glyphicon-ok" aria-hidden="true" ng-show="roleFoo"></span></div>

  </div>    

   </div>

  </div>

 </div>

</div>

</body>

</html>

app.js

var appModule = angular.module('myApp', []);

appModule.controller('MainCtrl', ['mainService','$scope','$http',

        function(mainService, $scope$http) {

            $scope.greeting = 'Welcome to the JSON Web Token / AngularJR / Spring example!';

            $scope.token = null;

            $scope.error = null;

            $scope.roleUser = false;

            $scope.roleAdmin = false;

            $scope.roleFoo = false;

            $scope.login = function() {

                $scope.error = null;

                mainService.login($scope.userName).then(function(token) {

                    $scope.token = token;

                    $http.defaults.headers.common.Authorization = 'Bearer ' + token;

                    $scope.checkRoles();

                },

                function(error){

                    $scope.error = error

                    $scope.userName = '';

                });

            }

            $scope.checkRoles = function() {

                mainService.hasRole('user').then(function(user) {$scope.roleUser = user});

                mainService.hasRole('admin').then(function(admin) {$scope.roleAdmin = admin});

                mainService.hasRole('foo').then(function(foo) {$scope.roleFoo = foo});

            }

            $scope.logout = function() {

                $scope.userName = '';

                $scope.token = null;

                $http.defaults.headers.common.Authorization = '';

            }

            $scope.loggedIn = function() {

                return $scope.token !== null;

            }

        } ]);

appModule.service('mainService'function($http) {

    return {

        login : function(username) {

            return $http.post('/user/login', {name: username}).then(function(response) {

                return response.data.token;

            });

        },

        hasRole : function(role) {

            return $http.get('/api/role/' + role).then(function(response){

                console.log(response);

                return response.data;

            });

        }

    };

});

运行应用

新知达人, 还在直接用 JWT 做鉴权?JJWT 真香

效果

新知达人, 还在直接用 JWT 做鉴权?JJWT 真香

新知达人, 还在直接用 JWT 做鉴权?JJWT 真香


更多“jwt”相关内容

更多“jwt”相关内容

新知精选

更多新知精选