springboot JWT 搭建授权服务器

目录

0 基本介绍

0.1 课程视频

0.2 架构逻辑图

0.2.1 登录JWT与授权服务器交互

0.2.2 登录成功后JWT与gateway-server交互 路由限制

1 JWT私钥公钥

1.1 安装git ->win系统右键 -> git bash here

1.2 生成私钥jks文件

1.3 用私钥jks文件解析出公钥

1.4 保存 BEGIN PUBLIC KEY到txt文件

2 搭建授权服务器

 2.1 加依赖

2.2 yml配置文件

2.3 AuthorizationServerConfig 

2.3.1 添加第三方的客户端

2.3.2 配置验证管理器

2.3.2.1 jwtTokenStore

2.3.2.2 私钥jks 放入resources文件夹

2.3.2.3 JwtAccessTokenConverter ->用私钥jks

2.3.2.4 转化成json格式

2.3.2.3 完整代码

2.4 WebSecurityConfig

2.4.1 AuthenticationManager 授权管理器 Bean

2.4.2 UserDetailsService 用户Bean

2.4.3 PasswordEncoder 密码加密器Bean

2.4.4 安全 跨域

2.4.5 WebSecurityConfig完整代码

3 postman 测试

3.1 测试获取token

3.2 解析token

4 网关gateway-server服务器 验证token是否存在

4.1 加依赖

4.2 yml 配置文件

4.3 过滤器 filter

4.3.1 判断该接口是否需要token

4.3.2 从请求头里读取token 

4.3.3 如果没有token 给用户响应错误

4.3.4 过滤器拦截到用户的请求后->判断redis中是否存在

4.3.5 完整代码

5 postman 测试


0 基本介绍

0.1 课程视频

https://www.bilibili.com/video/BV173411P7M2?p=30&spm_id_from=pageDriver&vd_source=ff8b7f852278821525f11666b36f180a

0.2 架构逻辑图

0.2.1 登录 JWT 授权服务器 Redis

0.2.2 登录成功后JWT与gateway-server交互 路由限制

1 JWT私钥公钥

1.1 安装git ->win系统右键 -> git bash here

1.2 生成私钥jks文件

keytool -genkeypair -alias coinexchange -keyalg RSA -keypass coinexchange
-keystore coinexchange.jks -validity 365 -storepass coinexchange

1.3 用私钥jks文件解析出公钥

keytool -list -rfc --keystore coinexchange.jks | openssl x509 -inform pem
-pubkey

1.4 保存 BEGIN PUBLIC KEY到txt文件

2 搭建授权服务器

 2.1 加依赖

<!--        服务的发现和注册-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!--        OAuth2.0-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
<!--        web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

2.2 yml配置文件

server:
  port: 9999
spring:
  application:
    name: authorization-server
  cloud:
    nacos:
      discovery:
        server-addr: nacos-server:8848

2.3 AuthorizationServerConfig 

2.3.1 添加第三方的客户端

@Autowired
    private PasswordEncoder passwordEncoder; // 密码加密器 
/** 必须配一 ClientDetailsServiceConfigurer
     * 添加第三方的客户端
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //先配置到内存测试 之后配置到数据库使用
        clients.inMemory() //使用in-memory存储
                .withClient("coin-api") // 第三方客户端的名称 client_id
                .secret(passwordEncoder.encode("coin-secret")) //  第三方客户端的密钥 加密
                .scopes("all") //第三方客户端的授权范围 // 允许的授权范围
                .authorizedGrantTypes("password","refresh_token") // token 过期时间
                .accessTokenValiditySeconds(7 * 24 *3600) // token的有效期
                .refreshTokenValiditySeconds(30 * 24 * 3600)// refresh_token的有效期
                .and()
                .withClient("inside-app")
                .secret(passwordEncoder.encode("inside-secret"))
                .authorizedGrantTypes("client_credentials")
                .scopes("all")
                .accessTokenValiditySeconds(7 * 24 *3600)
        ;
        super.configure(clients);
    }

2.3.2 配置验证管理器

2.3.2.1 jwtTokenStore

 private TokenStore jwtTokenStore() {
        JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter());
        return jwtTokenStore;
    }

2.3.2.2 私钥jks 放入resources文件夹

2.3.2.3 JwtAccessTokenConverter ->用私钥jks

public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();

        // 加载我们的私钥 // 要生成一个 放在resources中
        ClassPathResource classPathResource = new ClassPathResource("coinexchange.jks");
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource, "coinexchange".toCharArray());
        tokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("coinexchange", "coinexchange".toCharArray()));
        return tokenConverter;
    }

2.3.2.4 转化成json格式

@Autowired
    private AuthenticationManager authenticationManager;
@Qualifier("userServiceDetailsServiceImpl") // 两种userDetailsService 指定?
    @Autowired
    private UserDetailsService userDetailsService;
/**必须配2 AuthorizationServerEndpointsConfigurer
     * 配置验证管理器,UserdetailService
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager) // 验证管理器
                .userDetailsService(userDetailsService)
                 .tokenStore(jwtTokenStore())// tokenStore 来存储我们的token jwt 存储token
                 .tokenEnhancer(jwtAccessTokenConverter()); // 转成json存储

        super.configure(endpoints);
    }

   

2.3.2.3 完整代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.web.bind.annotation.CrossOrigin;

@EnableAuthorizationServer // 开启授权服务器的功能
@Configuration
@CrossOrigin // @allowOriginsPattens
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { // 点进去有三个方法 就是要配的三个

    @Autowired
    private PasswordEncoder passwordEncoder; // 密码加密器

    @Autowired
    private AuthenticationManager authenticationManager;

//    @Qualifier("userServiceDetailsServiceImpl")
    @Qualifier("userServiceDetailsServiceImpl") // 两种userDetailsService 指定?
    @Autowired
    private UserDetailsService userDetailsService;



    /** 必须配一 ClientDetailsServiceConfigurer
     * 添加第三方的客户端
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //先配置到内存测试 之后配置到数据库使用
        clients.inMemory() //使用in-memory存储
                .withClient("coin-api") // 第三方客户端的名称 client_id
                .secret(passwordEncoder.encode("coin-secret")) //  第三方客户端的密钥 加密
                .scopes("all") //第三方客户端的授权范围 // 允许的授权范围
                .authorizedGrantTypes("password","refresh_token") // token 过期时间
                .accessTokenValiditySeconds(7 * 24 *3600) // token的有效期
                .refreshTokenValiditySeconds(30 * 24 * 3600)// refresh_token的有效期
                .and()
                .withClient("inside-app")
                .secret(passwordEncoder.encode("inside-secret"))
                .authorizedGrantTypes("client_credentials")
                .scopes("all")
                .accessTokenValiditySeconds(7 * 24 *3600)
        ;
        super.configure(clients);
    }

    /**必须配2 AuthorizationServerEndpointsConfigurer
     * 配置验证管理器,UserdetailService
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager) // 验证管理器
                .userDetailsService(userDetailsService)
                .tokenStore(jwtTokenStore())// tokenStore 来存储我们的token jwt 存储token
                .tokenEnhancer(jwtAccessTokenConverter()); // 转成json存储

        super.configure(endpoints);
    }

    private TokenStore jwtTokenStore() { // TokenStore接口 内存储存适合一台授权服务器,内存不能在多个服务器共享数据 -> redis 共享token
        JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter());
        return jwtTokenStore;
    }

    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();

        // 加载我们的私钥 // 要生成一个 放在resources中
        ClassPathResource classPathResource = new ClassPathResource("coinexchange.jks");
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource, "coinexchange".toCharArray());
        tokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("coinexchange", "coinexchange".toCharArray()));
        return tokenConverter;
    }

}

2.4 WebSecurityConfig

2.4.1 AuthenticationManager 授权管理器 Bean

@Bean // AuthorizationServerConfig 中的授权管理器
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();

2.4.2 UserDetailsService 用户Bean

//    @Bean //  没有搭建amdmin服务器 用假数据测试
//    public UserDetailsService userDetailsService() {
//        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
//        User user = new User("admin", "123456", Arrays.asList(new SimpleGrantedAuthority("Role_Admin")));
//        inMemoryUserDetailsManager.createUser(user);
//        return inMemoryUserDetailsManager;
//    }

2.4.3 PasswordEncoder 密码加密器Bean

@Bean // 密码加密器
    public PasswordEncoder passwordEncoder() { //单向校验安全性高,但开销很大,单次密码校验耗时可能高达1秒
        return new BCryptPasswordEncoder();
    }

2.4.4 安全 跨域

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable(); // 跨域
        http.authorizeRequests().anyRequest().authenticated();

2.4.5 WebSecurityConfig完整代码

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

import java.util.Arrays;

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable(); // 跨域
        http.authorizeRequests().anyRequest().authenticated(); // 授权
    }

    @Bean // AuthorizationServerConfig 中的授权管理器
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }


//    @Bean //  没有搭建amdmin服务器 用假数据测试
//    public UserDetailsService userDetailsService() {
//        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
//        User user = new User("admin", "123456", Arrays.asList(new SimpleGrantedAuthority("Role_Admin")));
//        inMemoryUserDetailsManager.createUser(user);
//        return inMemoryUserDetailsManager;
//    }

    /**
     * 密码加密
     *
     * @return
     */
    @Bean // 密码加密器
    public PasswordEncoder passwordEncoder() { //单向校验安全性高,但开销很大,单次密码校验耗时可能高达1秒
        return new BCryptPasswordEncoder();
    }

    //测试密码加密器
    public static void main(String[] args) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode("123456");
        System.out.println(encode);
    }
}

3 postman 测试

3.1 测试获取token

3.2 解析token

https://jwt.io

4 网关gateway-server服务器 验证token是否存在

4.1 加依赖

        <!--因为授权服务器判断token 要通过网关 所以redis添加到网关服务器-->
        <dependency> 
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

4.2 yml 配置文件

  redis:
    host: redis-server
    port:
    password: 

4.3 过滤器 filter

4.3.1 判断该接口是否需要token

@Value("${no.require.urls:/admin/login,/user/gt/register}")
    private Set<String> noRequireTokenUris ;
private boolean isRequireToken(ServerWebExchange exchange) {
        String path = exchange.getRequest().getURI().getPath();
        if(noRequireTokenUris.contains(path)){
            return false ; // 不需要token
        }
        if(path.contains("/kline/")){
            return false ;
        }
        return Boolean.TRUE ;
    }

4.3.2 从请求头里读取token 

private String getUserToken(ServerWebExchange exchange) {
        String token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        return token ==null ? null : token.replace("bearer ","") ;
    }

4.3.3 如果没有token 给用户响应错误

private Mono<Void> buildeNoAuthorizationResult(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().set("Content-Type","application/json"); // 相应类型 json数据
        response.setStatusCode(HttpStatus.UNAUTHORIZED) ; // 相应码 401 
        JSONObject jsonObject = new JSONObject(); // 创建json对象
        jsonObject.put("error","NoAuthorization") ;
        jsonObject.put("errorMsg","Token is Null or Error") ;
        DataBuffer wrap = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes()); // 响应数据
        return response.writeWith(Flux.just(wrap)) ; // 转为流式?Flux.just(wrap)
    }

4.3.4 过滤器拦截到用户的请求后->判断redis中是否存在

@Autowired
    private StringRedisTemplate redisTemplate ;
@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1 : 该接口是否需要token 才能访问
        if(!isRequireToken(exchange)){
            return chain.filter(exchange) ;// 不需要token ,直接放行
        }
        // 2: 取出用户的token
        String token = getUserToken(exchange) ;
        // 3 判断用户的token 是否有效
        if(StringUtils.isEmpty(token)){
            return buildeNoAuthorizationResult(exchange) ;
        }
        Boolean hasKey = redisTemplate.hasKey(token);
        if(hasKey!=null && hasKey){
            return chain.filter(exchange) ;// token有效 ,直接放行
        }
        return buildeNoAuthorizationResult(exchange) ;
    }

4.3.5 完整代码

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Set;

@Component // 放到容器
public class JwtCheckFilter implements GlobalFilter, Ordered {

    @Autowired
    private StringRedisTemplate redisTemplate ;

    @Value("${no.require.urls:/admin/login,/user/gt/register}")
    private Set<String> noRequireTokenUris ;
    /**
     * 过滤器拦截到用户的请求后做啥
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1 : 该接口是否需要token 才能访问
        if(!isRequireToken(exchange)){
            return chain.filter(exchange) ;// 不需要token ,直接放行
        }
        // 2: 取出用户的token
        String token = getUserToken(exchange) ;
        // 3 判断用户的token 是否有效
        if(StringUtils.isEmpty(token)){
            return buildeNoAuthorizationResult(exchange) ;
        }
        Boolean hasKey = redisTemplate.hasKey(token);
        if(hasKey!=null && hasKey){
            return chain.filter(exchange) ;// token有效 ,直接放行
        }
        return buildeNoAuthorizationResult(exchange) ;
    }

    /**
     * 给用户响应一个没有token的错误
     * @param exchange
     * @return
     */
    private Mono<Void> buildeNoAuthorizationResult(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().set("Content-Type","application/json"); // 相应json数据
        response.setStatusCode(HttpStatus.UNAUTHORIZED) ;
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("error","NoAuthorization") ;
        jsonObject.put("errorMsg","Token is Null or Error") ;
        DataBuffer wrap = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes());
        return response.writeWith(Flux.just(wrap)) ;
    }

    /**
     * 从 请求头里面获取用户的token
     * @param exchange
     * @return
     */
    private String getUserToken(ServerWebExchange exchange) {
        String token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        return token ==null ? null : token.replace("bearer ","") ; // 去掉bearer
    }

    /**
     * 判断该 接口是否需要token
     * @param exchange
     * @return
     */
    private boolean isRequireToken(ServerWebExchange exchange) {
        String path = exchange.getRequest().getURI().getPath();
        if(noRequireTokenUris.contains(path)){
            return false ; // 不需要token
        }
        if(path.contains("/kline/")){
            return false ;
        }
        return Boolean.TRUE ;
    }


    /**
     * 拦截器的顺序
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

5 postman 测试

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值