base64编码 springboot_Spring Boot整合JWT实现用户认证(附源码)

JWT(Json Web Token)是一种用于安全地在用户和服务器之间传递信息的工具。它通过三个部分(Header、Payload和Signature)构成,允许在客户端存储认证信息,减轻服务器负担。本文详细介绍了JWT的工作原理、组成部分以及在SpringBoot应用中的实现,包括用户登录后的JWT生成和验证过程。通过示例代码展示了JWT的创建和验证方法,并提供了测试流程。

作者:LTLAYX

初探 JWT

什么是 JWT

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

为什么要用 JWT

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

JWT 长什么样

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

Header

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

"typ":"JWT", 

"alg":"HS256" 

Claim

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

"iss":"Issuer —— 用于说明该JWT是由谁签发的", 

"sub":"Subject —— 用于说明该JWT面向的对象", 

"aud":"Audience —— 用于说明该JWT发送给的用户", 

"exp":"Expiration Time —— 数字类型,说明该JWT过期的时间", 

"nbf":"Not Before —— 数字类型,说明在该时间之前JWT不能被接受与处理", 

"iat":"Issued At —— 数字类型,说明该JWT何时被签发", 

"jti":"JWT ID —— 说明标明JWT的唯一ID", 

"user-definde1":"自定义属性举例", 

"user-definde2":"自定义属性举例" 

Signature

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

总结

b71a21e1a1a63422e83295966be6a1ac.png

JWT 实现认证的原理

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

JWT 实现用户认证的流程图

e680baa38855b8efd4c9aed9d3efe026.png

JWT 的代码实现

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

http://blog.youkuaiyun.com/ltl112358/article/details/79399026

代码说明:

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

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

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

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

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

c719482d265f715362d0a0d1b758cb90.png

JwtCfg 类

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

/**

 * This is Jwt configuration which set the url "/secure/*" for filtering

 * @program: users

 * @create: 2018-03-03 21:18

 **/

@Configuration

public class JwtCfg {

    @Bean

    public FilterRegistrationBean jwtFilter() {

        final FilterRegistrationBean registrationBean = new FilterRegistrationBean();

        registrationBean.setFilter(new JwtFilter());

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

        return registrationBean;

    }

}

JwtFilter 类

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

/**

 * Check the jwt token from front end if is invalid

 * @program: users

 * @create: 2018-03-01 11:03

 **/

public class JwtFilter extends GenericFilterBean {

    public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)

            throws IOException, ServletException {

        // Change the req and res to HttpServletRequest and HttpServletResponse

        final HttpServletRequest request = (HttpServletRequest) req;

        final HttpServletResponse response = (HttpServletResponse) res;

        // Get authorization from Http request

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

        // If the Http request is OPTIONS then just return the status code 200

        // which is HttpServletResponse.SC_OK in this code

        if ("OPTIONS".equals(request.getMethod())) {

            response.setStatus(HttpServletResponse.SC_OK);

            chain.doFilter(req, res);

        }

        // Except OPTIONS, other request should be checked by JWT

        else {

            // Check the authorization, check if the token is started by "Bearer "

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

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

            }

            // Then get the JWT token from authorization

            final String token = authHeader.substring(7);

            try {

                // Use JWT parser to check if the signature is valid with the Key "secretkey"

                final Claims claims = Jwts.parser().setSigningKey("secretkey").parseClaimsJws(token).getBody();

                // Add the claim to request header

                request.setAttribute("claims", claims);

            } catch (final SignatureException e) {

                throw new ServletException("Invalid token");

            }

            chain.doFilter(req, res);

        }

    }

}

PersonController 类

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

/**

 * @program: users

 * @create: 2018-02-27 19:28

 **/

@RestController

public class PersonController {

    @Autowired

    private PersonService personService;

    /**

     * User register with whose username and password

     * @param reqPerson

     * @return Success message

     * @throws ServletException

     */

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

    public String register(@RequestBody() ReqPerson reqPerson) throws ServletException {

        // Check if username and password is null

        if (reqPerson.getUsername() == "" || reqPerson.getUsername() == null

                || reqPerson.getPassword() == "" || reqPerson.getPassword() == null)

            throw new ServletException("Username or Password invalid!");

        // Check if the username is used

        if(personService.findPersonByUsername(reqPerson.getUsername()) != null)

            throw new ServletException("Username is used!");

        // Give a default role : MEMBER

        List<Role> roles = new ArrayList<Role>();

        roles.add(Role.MEMBER);

        // Create a person in ignite

        personService.save(new Person(reqPerson.getUsername(), reqPerson.getPassword(), roles));

        return "Register Success!";

    }

    /**

     * Check user`s login info, then create a jwt token returned to front end

     * @param reqPerson

     * @return jwt token

     * @throws ServletException

     */

    @PostMapping

    public String login(@RequestBody() ReqPerson reqPerson) throws ServletException {

        // Check if username and password is null

        if (reqPerson.getUsername() == "" || reqPerson.getUsername() == null

                || reqPerson.getPassword() == "" || reqPerson.getPassword() == null)

            throw new ServletException("Please fill in username and password");

        // Check if the username is used

        if(personService.findPersonByUsername(reqPerson.getUsername()) == null

                || !reqPerson.getPassword().equals(personService.findPersonByUsername(reqPerson.getUsername()).getPassword())){

            throw new ServletException("Please fill in username and password");

        }

        // Create Twt token

        String jwtToken = Jwts.builder().setSubject(reqPerson.getUsername()).claim("roles", "member").setIssuedAt(new Date())

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

        return jwtToken;

    }

}

SecureController 类

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

/**

 * Test the jwt, if the token is valid then return "Login Successful"

 * If is not valid, the request will be intercepted by JwtFilter

 * @program: users

 * @create: 2018-03-01 11:05

 **/

@RestController

@RequestMapping("/secure")

public class SecureController {

    @RequestMapping("/users/user")

    public String loginSuccess() {

        return "Login Successful!";

    }

}

代码功能测试

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

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

fb740af263957d18a7b37de01427fdd4.png

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

7c6dfad4e17cc2eeacc57a519fad5e17.png

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

cdddef3a9537665584975a4e9b6cfe7c.png

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

7db969f0a84467780eb990624842f78e.png

示例代码

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

参考

http://blog.leapoahead.com/2015/09/06/understanding-jwt/ ↩
https://aboullaite.me/spring-boot-token-authentication-using-jwt/

(完)

推荐阅读

IDEA新特性:提前知道代码怎么走

还在担心写的一手烂SQL,送你4款工

瞬间几千次的重复提交,我用 SpringBoot+Redis 扛住了

爬了知乎“神回复”,笑得根本停不下来

这 17 个 JVM 参数,高级 Java 必须掌握!

d5eb2e10d0762b3f4eb60cb9e18711a5.png

好文!必须点赞
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值