【java】springboot spring security + JWT鉴权

一些理解,不一定正确

在这里插入图片描述

请求通过API,进入Basic Authentication Filter,在这里拿到token,解析token,拿到username和password,将用户输入的用户名密码进行封装,并提供给AuthenticationManager.authenticate()方法进行验证,验证成功后,返回一个认证成功的UsernamePasswordAuthenticationToken(Authentication)对象,然后放在security的context中,继续过滤链。

如果没有token,或者token不对,则继续过滤链,到达UsernamePasswordAuthenticationFilter,在attemptAuthentication()方法中,从请求的header中,拿到user和password,调用AuthenticationManager.authenticate()进行认证

AuthenticationManager验证过程

AuthenticationManager.authenticate()接收Authentication对象作为参数,由其实现类ProviderManager完成对其进行验证

在ProviderManager的authenticate方法中,轮训成员变量List providers。该providers中如果有一个

AuthenticationProvider的supports函数返回true,那么就会调用该AuthenticationProvider的authenticate函数认证,调用SecurityConfig中指定的userService,没有则使用UserDetailsService ,调用loadUserByUsername() 方法从数据库中加载用户信息 ,对密码等进行验证,整个认证过程结束。如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证,只要有一个认证成功则为认证成功。

参考大佬的验证原理

代码

spring security + JWT依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.19.2</version>
        </dependency>

创建SecurityConfig类,配置springSecurity

package xyz.heguchangan.easymusicapp.config;

import org.springframework.beans.factory.annotation.Autowired;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import xyz.heguchangan.easymusicapp.exception.RestAuthenticationEntryPoint;
import xyz.heguchangan.easymusicapp.filter.JwtAuthenticationFilter;
import xyz.heguchangan.easymusicapp.filter.JwtAuthorizationFilter;
import xyz.heguchangan.easymusicapp.service.UserService;

/**
 * springSecurity的配置文件
 * @Configuration:声明为配置类
 * @EnableWebSecurity:声明该类为springSecurity的配置类
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /** 密钥 */
    public static final String SECRET = "heguchangan";

    /**JWT令牌过期时间*/
    public static final long EXPIRATION_TIME = 864000000;

    /** TOKEN的前缀*/
    public static final String TOKEN_PREFIX = "Bearer";

    /** header中,token对应的变量名*/
    public static final String HEADER_STRING = "Authorization";

    /** 注册的入口,注册不开启JWT鉴权*/
    public static final String SIGN_UP_URL = "/users/";

    
    /** 自动装载bean */
    UserService userService;
    RestAuthenticationEntryPoint restAuthenticationEntryPoint;
    
    @Autowired
    public void setRestAuthenticationEntryPoint(RestAuthenticationEntryPoint restAuthenticationEntryPoint) {
        this.restAuthenticationEntryPoint = restAuthenticationEntryPoint;
    }
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    /*
    * 配置springSecurity
    * */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /* .cors()开启跨域 
        * .csrf().disable() csrf关闭 
        * .authorizeRequests()开启request的鉴权
        * antMatchers.permitAll(类型,地址)开启白名单,SIGN_UP_URL地址的post请求类型全部允许
        * anyRequest() 剩下的所有request都需要鉴权
        * addFilter 使用拦截器,这两个拦截器后面会讲
        * 然后exceptionHandling对错误码进行处理 重写authenticationEntryPoint()类自定义错误页面
        * 再把session设置成无状态
        * */
        http.cors()
                .and()
                .csrf().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.POST,SIGN_UP_URL).permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilter(new JwtAuthenticationFilter(authenticationManager()))
                .addFilter(new JwtAuthorizationFilter(authenticationManager()))
                .exceptionHandling()
                .authenticationEntryPoint(restAuthenticationEntryPoint)
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        /** 指定service,这样通过username从数据库里返回数据时,就会使用userService里的方法*/
        authenticationManagerBuilder.userDetailsService(userService);
    }
}

两个过滤器

JwtAuthorizationFilter

package xyz.heguchangan.easymusicapp.filter;


import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import xyz.heguchangan.easymusicapp.config.SecurityConfig;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;

/*
* 授权拦截器,如果当前request有token的话
* 从Header中拿到token,对token进行鉴权
* 然后把用户放到context中
* */
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
    //构造函数
    public JwtAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }


    /** 重写方法 */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

        // 拿到header中的token
        String token = request.getHeader(SecurityConfig.HEADER_STRING);
        //没有header,或者token不是规定的前缀
        if(token == null || !token.startsWith(SecurityConfig.TOKEN_PREFIX)){
            // 不做处理,给下一个过滤器,还可以通过username和password进行鉴权
            chain.doFilter(request,response);
            return;
        }

        // 进行鉴权,拿到鉴权之后的Authentication对象
        UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(token);
        // 把Authentication对象放在security的context中,继续过滤链
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        chain.doFilter(request,response);

    }
    /*
    * 进行token鉴权
    * */
    private UsernamePasswordAuthenticationToken getAuthentication(String header){
        if (header != null) {
            /* HMAC512签名解码,build之后,验证(需要把前缀拿掉)拿到username */
            String username = JWT.require(Algorithm.HMAC512(SecurityConfig.SECRET))
                    .build()
                    .verify(header.replace(SecurityConfig.TOKEN_PREFIX, ""))
                    .getSubject();
            if (username != null) {
                /** 如果username不为空,username鉴权*/
                User user = userService.loadUserByUsername(username);
                return new UsernamePasswordAuthenticationToken(username, user.getPassword(), new ArrayList<>());
            }
        }
        return null;
    }
}

JwtAuthenticationFilter

该拦截器通过username和password进行鉴权

package xyz.heguchangan.easymusicapp.filter;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.fasterxml.jackson.databind.ObjectMapper;
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.web.authentication.UsernamePasswordAuthenticationFilter;
import xyz.heguchangan.easymusicapp.config.SecurityConfig;
import xyz.heguchangan.easymusicapp.entity.User;
import xyz.heguchangan.easymusicapp.exception.BizException;
import xyz.heguchangan.easymusicapp.exception.ExceptionType;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;

/*
* JWT鉴权拦截器,
* 该类主要处理拿到username和password
* 进行鉴权
* */
// 因为是针对username 和password 所以继承UsernamePasswordAuthenticationFilter类
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    /** 不用管 在springSecurity的config中会传来这个类 */
    private final AuthenticationManager authenticationManager;
    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    /** 尝试对请求进行鉴权*/
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            /**获取输入中的username和pwd */
            //从输入流中映射json对应的属性给user对象,就是把json转换成user对象
            User user = new ObjectMapper().
                    readValue(request.getInputStream(),User.class);
            // 通过username和pwd生成token,对token鉴权返回结果
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            user.getUsername(),
                            user.getPassword(),
                            new ArrayList<>()
                    )
            );
        } catch (IOException e) {
            e.printStackTrace();
            /* 抛出自定义错误 */
            throw new BizException(ExceptionType.FORBIDDEN);
        }
    }

    /** 鉴权成功的处理 */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        /** 创建JWT的token
         * withSubject设置规则为username,authResult.getPrincipal()返回object需要转成user
         * .withExpiresAt设置到期时间,当前时间加上存活的时间
         * .sign HMAC512用设定好的字符串签名*/
        String token = JWT.create()
                .withSubject(((User)authResult.getPrincipal()).getUsername())
                .withExpiresAt(new Date(System.currentTimeMillis()+ SecurityConfig.EXPIRATION_TIME))
                .sign(Algorithm.HMAC512(SecurityConfig.SECRET));
        /** 添加到Header中,name+value*/
        response.addHeader(SecurityConfig.HEADER_STRING,SecurityConfig.TOKEN_PREFIX+token);
    }
}

UserService

springSecurity底层需要访问数据库,需要继承UserDetailsService,实现loadUserByUsername方法


public interface UserService extends UserDetailsService {

    /*通过username,从数据库拿出user信息*/
    @Override
    User loadUserByUsername(String username) throws UsernameNotFoundException;

}

同时 user类需要实现UserDetails

public class User implements UserDetails {

	....

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值