博客
关于我
使用JWT作为Spring Security OAuth2的token存储
阅读量:418 次
发布时间:2019-03-06

本文共 9297 字,大约阅读时间需要 30 分钟。

Spring Security OAuth2的demo在前几篇文章中已经讲过了,在那些模式中使用的都是RemoteTokenService调用授权服务器来校验token,返回校验通过的用户信息供上下文中获取

这种方式会加重授权服务器的负载,你想啊,当用户没授权时候获取token得找授权服务器,有token了访问资源服务器还要访问授权服务器,相当于说每次请求都要访问授权服务器,这样对授权服务器的负载会很大

常规的方式有两种来解决这个问题:

  1. 使用JWT作为Token传递
  2. 使用Redis存储Token,资源服务器本地访问Redis校验Token

使用JWT与Redis都可以在资源服务器中进行校验Token,从而减少授权服务器的工作量

JWT默认使用HMACSHA256对称加密算法,以下记录下默认算法实现与非对称RSA算法的集成,使用不同算法加解密测试方法是一致的,所以放在文章最后

授权服务器整合JWT——对称加解密算法

授权服务器整体代码结构

pom.xml中引入依赖

org.springframework.boot
spring-boot-starter-security
2.2.1.RELEASE
org.springframework.security.oauth
spring-security-oauth2
2.4.0.RELEASE
org.springframework.security
spring-security-jwt
1.1.0.RELEASE

SecurityConfig配置,主要需要显式声明AuthenticationManager和UserDetailsService这两个bean

@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    @Bean    public AuthenticationManager authenticationManager() throws Exception {        return super.authenticationManager();    }    @Bean    public UserDetailsService userDetailsService(){ //主要是配置这个Bean,用于授权服务器配置中注入        return super.userDetailsService();    }    @Bean    public PasswordEncoder passwordEncoder(){        return new BCryptPasswordEncoder();    }    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        // @formatter: off        auth.inMemoryAuthentication()                .withUser("hellxz")                .password(passwordEncoder().encode("xyz"))                .authorities(Collections.emptyList());        // @formatter: on    }    @Override    protected void configure(HttpSecurity http) throws Exception {        http.authorizeRequests()                .anyRequest().authenticated() //所有请求都需要通过认证                .and()                .httpBasic() //Basic提交                .and()                .csrf().disable(); //关跨域保护    }}

授权服务器配置AuthorizationConfig

@Configuration@EnableAuthorizationServer //开启授权服务public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {    @Autowired    private AuthenticationManager authenticationManager;    @Autowired    public UserDetailsService userDetailsService;    @Autowired    private PasswordEncoder passwordEncoder;    @Override    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {        //允许表单提交        security.allowFormAuthenticationForClients()                .checkTokenAccess("permitAll()")                .tokenKeyAccess("permitAll()");    }    @Override    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {        // @formatter: off        clients.inMemory()                .withClient("client-a") //client端唯一标识                    .secret(passwordEncoder.encode("client-a-secret")) //client-a的密码,这里的密码应该是加密后的                    .authorizedGrantTypes("authorization_code", "password", "refresh_token") //授权模式标识,这里主要测试用password模式,另外refresh_token不是一种模式,但是可以使用它来刷新access_token(在它的有效期内)                    .scopes("read_user_info") //作用域                    .resourceIds("resource1") //资源id                    .redirectUris("http://localhost:9001/callback"); //回调地址        // @formatter: on    }    @Override    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {        endpoints.authenticationManager(authenticationManager)                .userDetailsService(userDetailsService)                .tokenStore(jwtTokenStore()) //设置jwtToken为tokenStore                .accessTokenConverter(jwtAccessTokenConverter());//设置access_token转换器    }    /**     * jwt访问token转换器     */    @Bean    public JwtAccessTokenConverter jwtAccessTokenConverter(){        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();        converter.setSigningKey("my-sign-key"); //资源服务器需要配置此选项方能解密jwt的token        return converter;    }    /**     * jwt的token存储对象     */    @Bean    public JwtTokenStore jwtTokenStore(){        return new JwtTokenStore(jwtAccessTokenConverter());    }}

这里主要是在configure(AuthorizationServerEndpointsConfigurer endpoints)授权服务的端点配置中加入JWT的tokenStore和access_token的转换器,以及这二者的声明Bean方法

这里使用的是默认对称MAC算法,即加密解密使用相同的密钥

启动类就不说了,开启@SpringBootApplicatin的main方法

资源服务器整合JWT——对称加解密算法

资源服务器主要就一个资源配置类

@Configuration@EnableResourceServerpublic class ResourceConfig extends ResourceServerConfigurerAdapter {    @Bean    public PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }    @Override    public void configure(HttpSecurity http) throws Exception {        //设置创建session策略        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);        //@formatter:off        //所有请求必须授权        http.authorizeRequests()                .anyRequest().authenticated();        //@formatter:on    }    @Override    public void configure(ResourceServerSecurityConfigurer resources) {        resources.tokenStore(jwtTokenStore());    }    /**     * jwt访问token转换器     */    @Bean    public JwtAccessTokenConverter jwtAccessTokenConverter(){        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();        converter.setSigningKey("my-sign-key"); //与授权服务器相同的signingKey        return converter;    }    /**     * jwt的token存储对象     */    @Bean    public JwtTokenStore jwtTokenStore(){        return new JwtTokenStore(jwtAccessTokenConverter());    }}

配置JWT的TokenStore和AccessTokenConverter与授权服器相同,添加启动类完成配置

OAuth整合JWT——非对称加解密RSA

本部分基于对称加密部分,仅展示需要修改的部分

首先使用keytool生成jks (Java Key Store) 密钥,按提示输入姓氏等信息

keytool -genkeypair -alias hellxz-jwt -validity 3650 -keyalg RSA -keypass hellxzTest -keystore hellxz-jwt.jks -storepass hellxzTest

生成的私钥文件会在当前目录,把hellxz-jwt.jks复制到授权服务器的resources目录下

授权服务器需修改jwtAccessTokenConverter()

@Bean    public JwtAccessTokenConverter jwtAccessTokenConverter(){        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();        KeyStoreKeyFactory storeKeyFactory = new KeyStoreKeyFactory(                new ClassPathResource("hellxz-jwt.jks"), "hellxzTest".toCharArray());        converter.setKeyPair(storeKeyFactory.getKeyPair("hellxz-jwt"));        return converter;    }

在hellxz-jwt.jks同目录下,执行命令生成公钥

➜ keytool -list -rfc --keystore hellxz-jwt.jks | openssl x509 -inform pem -pubkey输入密钥库口令:  hellxzTest-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxU7zulFUVBXmZD28xwM4ul5e9yFrToLgWKHlNLlp904/GbiWBoZ4tcBcNq3VxLGBN9VOqfP1P5C7fRgz95UI7ShKCKgsFFGL2rAqsplMDClN/adfsxmpF06rVIkGgce9tR0Q0iONcaN+b/lArK4TAu76QsQwn9MLXlznVfczclZOZSfDNju+1JuBzqt6fEPWqalBUVYdV0zCUDG8ikN1l9D0m1tSSaKpiTrU2yEUGUji+79Ury7Y8BClEX6d4CTl9TQAhL5g32GoJEc0S2y+0bqeqUsv1nUt9KiJT9kiOvA+Q7o2T8OHuqQT9le7kvmIi4gSX5vSNvvZagE2UglhzQIDAQAB-----END PUBLIC KEY----------BEGIN CERTIFICATE-----MIIDUTCCAjmgAwIBAgIEePeDczANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJDTjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEKMAgGA1UEChMBMDEKMAgGA1UECxMBMDEOMAwGA1UEAxMFemhhbmcwHhcNMTkxMjE1MDUyOTM2WhcNMjkxMjEyMDUyOTM2WjBZMQswCQYDVQQGEwJDTjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEKMAgGA1UEChMBMDEKMAgGA1UECxMBMDEOMAwGA1UEAxMFemhhbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDFTvO6UVRUFeZkPbzHAzi6Xl73IWtOguBYoeU0uWn3Tj8ZuJYGhni1wFw2rdXEsYE31U6p8/U/kLt9GDP3lQjtKEoIqCwUUYvasCqymUwMKU39p1+zGakXTqtUiQaBx721HRDSI41xo35v+UCsrhMC7vpCxDCf0wteXOdV9zNyVk5lJ8M2O77Um4HOq3p8Q9apqUFRVh1XTMJQMbyKQ3WX0PSbW1JJoqmJOtTbIRQZSOL7v1SvLtjwEKURfp3gJOX1NACEvmDfYagkRzRLbL7Rup6pSy/WdS30qIlP2SI68D5DujZPw4e6pBP2V7uS+YiLiBJfm9I2+9lqATZSCWHNAgMBAAGjITAfMB0GA1UdDgQWBBQF96rK7n0XufnvtJuH9tD9Ixza6zANBgkqhkiG9w0BAQsFAAOCAQEAuMzWZJhej6+4TGgodQKQ5L5RBtOUbesxA1Ues9iA4m/jNZnVCXJE0nY47YVzBCIkIsYALswGooMj1PIJxEMpggXVmIuiJpaPgg+4sthzISxKzX0ru8IrJTapaglMi74ai6S73LTBSke9GEPgWWnbtdUZoUSiSNt1oJ0JEhFHdPuzxc36neDFRBOBxW4w3qhsTlKTN2wJm1nLV96nFKmqJhQJhhKt6ihe7hMgqWxzNsWAqv9gJNdKZt5teqwNKT6H7r1NX5oJkJ0Kn1dZy0O3rDDd5E0KDKkMtwOh3deJH6Uvtt/dw/drzJlByNDEPp6hYGQu2dW5JG5uiHuzFHnJeA==-----END CERTIFICATE-----

复制公钥部分到public.cert放到资源服务器的resources目录

-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxU7zulFUVBXmZD28xwM4ul5e9yFrToLgWKHlNLlp904/GbiWBoZ4tcBcNq3VxLGBN9VOqfP1P5C7fRgz95UI7ShKCKgsFFGL2rAqsplMDClN/adfsxmpF06rVIkGgce9tR0Q0iONcaN+b/lArK4TAu76QsQwn9MLXlznVfczclZOZSfDNju+1JuBzqt6fEPWqalBUVYdV0zCUDG8ikN1l9D0m1tSSaKpiTrU2yEUGUji+79Ury7Y8BClEX6d4CTl9TQAhL5g32GoJEc0S2y+0bqeqUsv1nUt9KiJT9kiOvA+Q7o2T8OHuqQT9le7kvmIi4gSX5vSNvvZagE2UglhzQIDAQAB-----END PUBLIC KEY-----

修改资源服务器jwtAccessTokenConverter()方法

@Bean    public JwtAccessTokenConverter jwtAccessTokenConverter(){        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();        Resource resource = new ClassPathResource("public.cert");        String publicKey;        try {            publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));        } catch (IOException e) {            throw new RuntimeException(e);        }        converter.setVerifierKey(publicKey);        return converter;    }

测试验证

发送POST请求http://localhost:8080/oauth/token?username=hellxz&password=xyz&scope=read_user_info&grant_type=password

返回结果

带token访问资源服务器

测试通过

另外使用JWT应设置尽量短的过期时间,因为JWT的token无法手动revoke,只能等待其到达过期时间失效

转载地址:http://kxauz.baihongyu.com/

你可能感兴趣的文章
【wp】HWS计划2021硬件安全冬令营线上选拔赛
查看>>
Ef+T4模板实现代码快速生成器
查看>>
JQuery选择器
查看>>
2.2.2原码补码移码的作用
查看>>
Java面试题:Servlet是线程安全的吗?
查看>>
Java集合总结系列2:Collection接口
查看>>
Linux学习总结(九)—— CentOS常用软件安装:中文输入法、Chrome
查看>>
比技术还重要的事
查看>>
linux线程调度策略
查看>>
软中断和实时性
查看>>
Linux探测工具BCC(可观测性)
查看>>
SNMP介绍及使用,超有用,建议收藏!
查看>>
HDU5589:Tree(莫队+01字典树)
查看>>
不停机替换线上代码? 你没听错,Arthas它能做到
查看>>
Python开发之序列化与反序列化:pickle、json模块使用详解
查看>>
采坑 - 字符串的 "" 与 pd.isnull()
查看>>
无序列表 - 链表
查看>>
Matplotlib绘制漫威英雄战力图,带你飞起来!
查看>>
机器学习是什么
查看>>
《小王子》里一些后知后觉的道理
查看>>