SpringBoot整合Jwt

jwt的简单概念

一、 JWT 是什么?

JWT 是一个开放标准,它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。

简单来说,就是通过一定规范来生成 token,然后可以通过解密算法逆向解密 token,这样就可以获取用户信息。

优点:

1)生产的 token 可以包含基本信息,比如 id、用户昵称、头像等信息,避免再次查库

2)存储在客户端,不占用服务端的内存资源

缺点:

token 是经过 base64 编码,所以可以解码,因此 token 加密前的对象不应该包含敏感信息,如用户权限,密码等

二、JWT 格式组成:头部、负载、签名

header(可反解)+payload(可反解)+signature(不可反解)

头部:主要是描述签名算法

负载:主要描述是加密对象的信息,如用户的id等,也可以加些规范里面的东西,如 iss 签发者,exp 过期时间,sub 面向的用户

签名:主要是把前面两部分进行加密,防止别人拿到 token 进行 base 解密后篡改 token

三、关于jwt客户端存储

可以存储在 Cookie,localStorage 和 sessionStorage 里面

jwt和springboot整合的基本栗子

一、添加依赖

		<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>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

二、config配置

import com.example.springbootjwt.filter.JwtFilter;
import com.example.springbootjwt.filter.JwtLoginFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @author 朝花不迟暮
 * @version 1.0
 * @date 2020/7/11 18:22
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Bean
    BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.inMemoryAuthentication().withUser("admin")
                .password("$2a$10$Tg3qH41r.M53Bse1axlUBefSpsEc88br3L.3NDTr2PmXwKsaAiJ12")
                .roles("admin")
                .and()
                .withUser("leo")
                .password("$2a$10$Tg3qH41r.M53Bse1axlUBefSpsEc88br3L.3NDTr2PmXwKsaAiJ12")
                .roles("user");
    }
}

这些都是security最基本的配置,没什么好说的。在security5之后,必须要加密,所以加了个BCryptPasswordEncoder的Bean,AuthenticationManagerBuilder是授权,给予了admin和user两个角色,这里的password必须是加密过后的,所以你去测试类里面把BCryptPasswordEncoder注入进去,使用encode方法

JjwtApplicationTests.java

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@SpringBootTest
class JjwtApplicationTests
{
    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;
    @Test
    void contextLoads()
    {
        System.out.println(bCryptPasswordEncoder.encode("123456"));
    }
}

三、写个控制层

HelloController.java

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

/**
 * @author 朝花不迟暮
 * @version 1.0
 * @date 2020/7/11 18:28
 */
@RestController
public class HelloController
{
    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }

    @GetMapping("/admin")
    public String admin(){
        return "admin";
    }
}

然后打开Google,输入 http://localhost:8080/hello,他会重定向到login进行登录,然后输入已经授权的用户名和密码,当然这只是看一下神奇(个屁)的security框架,下面还需要继续定义配置类。

四、配置一个实体类

user.java

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

/**
 * @author 朝花不迟暮
 * @version 1.0
 * @date 2020/7/11 18:32
 */
@Data
public class User implements UserDetails
{
    private String username;
    private String password;
    private List<GrantedAuthority> authorities;//角色

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities()
    {
        return authorities;
    }

    @Override
    public String getPassword()
    {
        return password;
    }

    @Override
    public String getUsername()
    {
        return username;
    }

    @Override
    public boolean isAccountNonExpired()
    {
        return true;
    }

    @Override
    public boolean isAccountNonLocked()
    {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired()
    {
        return true;
    }

    @Override
    public boolean isEnabled()
    {
        return true;
    }
}

基本配置,没什么好说的

五、定义过滤器,创建个filter包

JwtLoginFilter.java

import com.example.springbootjwt.model.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 朝花不迟暮
 * @version 1.0
 * @date 2020/7/11 18:35
 */
public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter
{
    private static final Log log= LogFactory.getLog(JwtLoginFilter.class);

    //这里要重写一个构造,参数是defaultFilterProcessesUrl,authenticationManager
    public JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager)
    {
        super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
        setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse resp) throws AuthenticationException, IOException, ServletException
    {
        //认证方式以json形式进行验证
        User user = new ObjectMapper().readValue(req.getInputStream(),User.class);
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
        return getAuthenticationManager().authenticate(token);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException
    {
        log.info("authResult:"+authResult.toString());
        /**
         * authResult:org.springframework.security.authentication.UsernamePasswordAuthenticationToken@3b57d2cd:
         * Principal: org.springframework.security.core.userdetails.User@586034f:
         * Username: admin; Password: [PROTECTED];
         * Enabled: true; AccountNonExpired: true;
         * credentialsNonExpired: true;
         * AccountNonLocked: true;
         * Granted Authorities: ROLE_admin;
         * Credentials: [PROTECTED];
         * Authenticated: true;
         * Details: null;
         * Granted Authorities: ROLE_admin
         */
        Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
        StringBuffer buffer = new StringBuffer();
        for (GrantedAuthority authority : authorities)
        {
            buffer.append(authority.getAuthority()).append(",");
        }

        //authorities:ROLE_admin,
        log.info("authorities:"+buffer);
        String jwt = Jwts.builder()
                .claim("authorities", buffer)
                //getName这个方法,在Authentication的继承类里面Principal
                .setSubject(authResult.getName())
                //设置过期时间
                .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 1000))
                //设置签名使用的签名算法和签名使用的秘钥
                .signWith(SignatureAlgorithm.HS512, "leo123456")
                .compact();
        Map<String, String> map = new HashMap<>();
        map.put("token",jwt);
        map.put("msg","登陆成功");
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(new ObjectMapper().writeValueAsString(map));
        writer.flush();
        writer.close();
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException
    {
        Map<String, String> map = new HashMap<>();
        map.put("msg","登陆失败");
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(new ObjectMapper().writeValueAsString(map));
        writer.flush();
        writer.close();
    }
}

这个配置首先 继承AbstractAuthenticationProcessingFilter,他重写的attemptAuthentication,它是把用户输入的用户名和密码获取到然后放到user的实体里面

UsernamePasswordAuthenticationToken继承AbstractAuthenticationToken实现Authentication
所以当在页面中输入用户名和密码之后首先会进入到UsernamePasswordAuthenticationToken验证(Authentication),
然后生成的Authentication会被交由AuthenticationManager来进行管理
而AuthenticationManager管理一系列的AuthenticationProvider,
而每一个Provider都会通UserDetailsService和UserDetail来返回一个
以UsernamePasswordAuthenticationToken实现的带用户名和密码以及权限的Authentication

然后重写两个方法,successfulAuthentication、unsuccessfulAuthentication,当然主要还是要看successfulAuthentication,看他的参数

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException

HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult

这里我想去看看authResult里面到底存放了些什么,不过我想可能存放了用户认证的一些基本信息

/**
         * authResult:org.springframework.security.authentication.UsernamePasswordAuthenticationToken@3b57d2cd:
         * Principal: org.springframework.security.core.userdetails.User@586034f:
         * Username: admin; Password: [PROTECTED];
         * Enabled: true; AccountNonExpired: true;
         * credentialsNonExpired: true;
         * AccountNonLocked: true;
         * Granted Authorities: ROLE_admin;
         * Credentials: [PROTECTED];
         * Authenticated: true;
         * Details: null;
         * Granted Authorities: ROLE_admin
         */

从打印的信息可以看到这里面有啥哦,Principal、Username用户名、Enabled是啥?不知道,我想大概是用户是否可用吧,credentialsNonExpired密码没有过期?AccountNonLocked账户没有被锁?等等等,大致就是你在user里面定义的那些东西。

生成jwt

String jwt = Jwts.builder()
                .claim("authorities", buffer)
                //getName这个方法,在Authentication的继承类里面Principal
                .setSubject(authResult.getName())
                //设置过期时间
                .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 1000))
                //设置签名使用的签名算法和签名使用的秘钥
                .signWith(SignatureAlgorithm.HS512, "leo123456")
                .compact();

把token放到map集合里面,然后把对象转成json字符串发给前端

JwtFilter.java

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;

/**
 * @author 朝花不迟暮
 * @version 1.0
 * @date 2020/7/11 19:50
 */
public class JwtFilter extends GenericFilterBean
{
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
    {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        String authorization = req.getHeader("authorization");
        Jws<Claims> jws = Jwts.parser().setSigningKey("leo123456").
                parseClaimsJws(authorization.replace("Bearer", ""));
        Claims body = jws.getBody();
        String username = body.getSubject();
        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(((String) body.get("authorities")));

        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, "", authorities);
        SecurityContextHolder.getContext().setAuthentication(token);
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

六、完善配置类

SecurityConfig.java

import com.example.springbootjwt.filter.JwtFilter;
import com.example.springbootjwt.filter.JwtLoginFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @author 朝花不迟暮
 * @version 1.0
 * @date 2020/7/11 18:22
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Bean
    BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.inMemoryAuthentication().withUser("admin")
                .password("$2a$10$Tg3qH41r.M53Bse1axlUBefSpsEc88br3L.3NDTr2PmXwKsaAiJ12")
                .roles("admin")
                .and()
                .withUser("leo")
                .password("$2a$10$Tg3qH41r.M53Bse1axlUBefSpsEc88br3L.3NDTr2PmXwKsaAiJ12")
                .roles("user");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http.authorizeRequests().antMatchers("/hello")
                .hasRole("user")
                .antMatchers("/admin")
                .hasRole("admin")
                .antMatchers(HttpMethod.POST,"/login")
                .permitAll()
                .anyRequest().authenticated()
                .and().addFilterBefore(new JwtLoginFilter("/login",authenticationManager()),
                UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new JwtFilter(),UsernamePasswordAuthenticationFilter.class)
        .csrf().disable();
    }
}

重写HttpSecurity的configure,关键是两个过滤器存放进来addFilterBefore把JwtLoginFilter和JwtFilter放进来

七、测试

使用postman,先登录
在这里插入图片描述
然后会得到

{
    "msg": "登陆成功",
    "token": "eyJhbGciOiJIUzUxMiJ9.eyJhdXRob3JpdGllcyI6IlJPTEVfYWRtaW4sIiwic3ViIjoiYWRtaW4iLCJleHAiOjE1OTQ0ODQ4NzF9.uKO8JJWT7EzDWJblV7UY1Oo9sHzfEDG3r1cZ2sTsNJHMixip-kfoGWyDj04OhM3UZ_Hv-8b1GeXX3Zib_PIcOA"
}

然后进入 http://localhost:8080/admin
在这里插入图片描述
在这里插入图片描述

总结

SpringBoot 整合SpringSecurity给我们的感觉就是很重,配置起来非常复杂,要记得地方很多。加上jwt我觉得也一样,配置起来比较复杂,如果不能系统的全面的了解这一套原理和流程,是很难理解和记忆的。而且这个案例是非常基本的,能大致的看出来jwt的基本的配置。

我在网上看过很多springboot整合jwt的案例,虽然各不相同但是大差不差,主要还是想去熟悉一下这些基本的流程,为后面整合OAuth2做一些准备。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值