在Oauth2中,总共有如下四种角色:
名称 | 英文名称 | 描述 |
---|---|---|
资源拥有者 | Resource Owner | 能够授予对受保护资源的访问权的实体。当资源所有者是一个人时,它就是用户。 |
资源服务器 | Resource Server | 承载受保护资源的服务器,能够使用访问令牌接受和响应受保护资源请求。是一个web站点或者web service API,用户的受保护数据保存于此。 |
客户端 | Client | 代表资源所有者及其授权发出受保护资源请求的应用程序。通常是一个web或者无线应用,它需要访问用户的受保护资源。 |
授权服务器 | Authorization Server | 在成功认证资源所有者并获得授权后,服务器向客户端发出访问令牌。 |
授权服务器可以是与资源服务器相同的服务器或单独的服务器。 单个授权服务器可以发出由多个资源服务器接受的访问令牌。 授权流程图大概如下:
从运行流程不难看出,要获取access token必须先得到用户授权(authorzation grant),那么如果获取这么用户授权呢?OAuth 2.0定义了四种类型的授权类型:
授权码模式是功能最完整、使用最广泛、流程最严密的授权模式。 由于这是一个基于重定向的流,所以客户端必须能够与资源所有者的用户代理(通常是web浏览器)进行交互,并且能够从授权服务器接收传入的请求(通过重定向)。
步骤如下:
下面接着介绍各个步骤所需的参数:
对于步骤A,客户端申请授权请求的URI,包含以下参数:
C步骤中,服务器回应客户端的URI,包含以下参数:
D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:
在 Spring Security Oauth2 中 上面的API 如下所示,这些 API 称为端点:
参数名称 | 是否必填 | 描述 |
---|---|---|
response_type | 必填 | 必须为 code |
client_id | 必填 | 客户端的ID |
redirect_uri | 可选 | 获取授权码后重定向地址 |
scope | 可选 | 申请的权限范围 |
state | 推荐 | 客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值,推荐。 |
授权成功的情况,会携带以下两个参数重定向到到 redirect_uri 中:
参数名称 | 是否必返回 | 描述 |
---|---|---|
code | 是 | 授权服务器生成的授权代码。授权代码必须在发布后不久过期,以降低泄漏的风险。最大授权代码生命周期为 10 分钟 |
state | 是 | 如果上一步中提供 state 参数,会原封不动地返回这个值。 |
参数名称 | 是否必填 | 描述 |
---|---|---|
grant_type | 必填 | 使用的授权模式,值固定为"authorization_code" |
code | 必填 | 上一步获得的授权码 |
redirect_uri | 必填 | 重定向 URI,必须与上一步中的该参数值保持一致 |
client_id | 必填 | 客户端的 ID |
scope | 推荐 | 授权范围,必须与第一步相同 |
如果访问令牌请求有效且经过授权,授权服务器将发出访问令牌和可选的刷新令牌,可以得到如下响应参数:
参数名称 | 是否必返回 | 描述 |
---|---|---|
access_token | 是 | 授权服务器颁发的访问令牌 |
token_type | 是 | 令牌类型,该值大小写不敏感,可以是 bearer 类型或 mac 类型 |
expires_in | 否(设置过期时间必返回) | 过期时间,单位为秒 |
refresh_token | 否 | 表示更新令牌,用来获取下一次的访问令牌 |
scope | 否 | 权限范围,如果有,则与客户端申请的范围一致 |
简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。具体步骤可参阅RFC6749 4.2( tools.ietf.org/html/rfc674… )节。
步骤如下:
密码模式中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。可参阅RFC6749 4.3( tools.ietf.org/html/rfc674… )节。
步骤如下:
该步骤中只设计到一个请求。在Spring Security Oauth2 中,该端点如下:
请求参数 | 是否必填 | 描述 |
---|---|---|
grant_type | 必填 | 使用的密码模式,值固定为 password |
username | 必填 | 用户名 |
password | 必填 | 密码 |
scope | 可选 | 请求权限范围 |
请求响应参考授权码模式。
客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。可参阅RFC6749 4.4( tools.ietf.org/html/rfc674… )节。
步骤如下:
该步骤中只设计到一个请求。在Spring Security Oauth2 中,该端点如下:
请求参数 | 是否必填 | 描述 |
---|---|---|
grant_type | 必填 | 使用的密码模式,值固定为 client_credentials |
scope | 可选 | 请求权限范围 |
请求响应参考授权码模式。
本案例主要演示密码模式的认证授权方式,后续在Spring Cloud中继承Oauth2再增加其他模式的案例;
pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <packaging>pom</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.hxmec</groupId> <artifactId>spring-security-oauth2-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-security-oauth2-demo</name> <description>spring security oauth2 demo</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <!-- jdk版本 --> <java.version>1.8</java.version> <!-- SpringCloud版本号,官方最新稳定版本 --> <spring-cloud.version>Hoxton.SR3</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.2.6.RELEASE</version> </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>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <resources> <!-- 先指定 src/main/resources下所有文件及文件夹为资源文件 --> <resource> <directory>src/main/resources</directory> <includes> <include>**/*</include> </includes> <filtering>true</filtering> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven-compiler-plugin.version}</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> <encoding>${maven.compiler.encoding}</encoding> </configuration> </plugin> <plugin> <!--打包跳过测试--> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${maven-surefire-plugin.version}</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> <!-- resources资源插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>${maven-resources-plugin.version}</version> <configuration> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>repackage</goal> <goal>build-info</goal> </goals> </execution> </executions> </plugin> <!-- java文档插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <version>3.0.0</version> </plugin> </plugins> </pluginManagement> </build> </project> 复制代码
授权服务器和资源服务器可以是同一台服务器,也可以是不同服务器,本案例中假设是同一台服务器,通过不同的配置开启授权服务器和资源服务器。
下面是授权服务器配置代码。创建一个自定义类继承自 AuthorizationServerConfigurerAdapter,完成对授权服务器的配置,然后通过 @EnableAuthorizationServer 注解开启授权服务器:
注意:authorizedGrantTypes("password", "refresh_token") 表示 OAuth 2 中的授权模式为“password”和“refresh_token”两种。在标准的 OAuth 2 协议中,授权模式并不包括“refresh_token”,但是在 Spring Security 的实现中将其归为一种,因此如果需要实现 access_token 的刷新,就需要这样一种授权模式。
AuthorizationServerConfig.java
/** * 功能描述: 授权服务器配置 * @author Trazen * @date 2020/7/8 22:26 */ /** * 功能描述: 授权服务器配置 * @author Trazen * @date 2020/7/8 22:26 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { /** * 该对象用来支持 password 模式 */ @Autowired private AuthenticationManager authenticationManager; /** * 该对象用来将令牌信息存储到内存中 * OAuth2令牌持久化主要有以下几种 * InMemoryTokenStore 内存存储 OAuth2默认储存方式 * JdbcTokenStore 数据库存储 * RedisTokenStore Redis存储 * JwkTokenStore & JwtTokenStore */ @Autowired(required = false) private TokenStore inMemoryTokenStore; /** * 该对象将为刷新token提供支持 * * InMemoryUserDetailsManager:从内存中加载用户账号 默认 * JdbcUserDetailsManager:从数据库中加载用户账号 */ @Autowired private UserDetailsService userDetailsService; /** * 指定密码的加密方式 * 推荐使用BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder等 * @return */ @Bean PasswordEncoder passwordEncoder() { // 使用BCrypt强哈希函数加密方案(密钥迭代次数默认为10) return new BCryptPasswordEncoder(); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { // 表示支持 client_id 和 client_secret 做登录认证 security.allowFormAuthenticationForClients(); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("password") //授权模式为password和refresh_token两种 .authorizedGrantTypes("password", "refresh_token") // 配置access_token的过期时间 .accessTokenValiditySeconds(1800) //配置资源id .resourceIds("rid") .scopes("all") //123加密后的密码 .secret("$2a$10$tnj.nZjSzCBckTh2fRRK9.ZTYfU0y4pDiZZChKxxeOElBsxaQCn26"); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //配置令牌的存储(这里存放在内存中) endpoints.tokenStore(inMemoryTokenStore) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } } 复制代码
自定义类继承自 ResourceServerConfigurerAdapter,并添加 @EnableResourceServer 注解开启资源服务器配置。
ResourceServerConfig.java
/** * 功能描述: 资源服务器配置 * @author Trazen * @date 2020/7/8 22:40 */ @Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer resources) { // 配置资源id,这里的资源id和授权服务器中的资源id一致 resources.resourceId("resId") // 设置这些资源仅基于令牌认证 .stateless(true); } /** * 配置 URL 访问权限 * @param http * @throws Exception */ @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("admin") .antMatchers("/user/**").hasRole("user") .anyRequest().authenticated(); } } 复制代码
这个 Spring Security 配置和上面的资源服务器配置中,都涉及到了 HttpSecurity。其中 Spring Security 中的配置优先级高于资源服务器中的配置,即请求地址先经过 Spring Security 的 HttpSecurity,再经过资源服务器的 HttpSecurity。
/** * 功能描述: Spring Security 配置 * @author Trazen * @date 2020/7/8 22:42 */ @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean @Override protected UserDetailsService userDetailsService() { return super.userDetailsService(); } /** * 增加一个admin角色用户,一个user角色用户 * admin----123456 * trazen----123456 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("admin") .password("$2a$10$tnj.nZjSzCBckTh2fRRK9.ZTYfU0y4pDiZZChKxxeOElBsxaQCn26") .roles("admin") .and() .withUser("trazen") .password("$2a$10$tnj.nZjSzCBckTh2fRRK9.ZTYfU0y4pDiZZChKxxeOElBsxaQCn26") .roles("user"); } /** * 开放oauth2授权端点 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/oauth/**").authorizeRequests() .antMatchers("/oauth/**").permitAll() .and().csrf().disable(); } } 复制代码
DemoController.java
/** * 功能描述: 测试接口 * @author Trazen * @date 2020/7/8 22:44 */ @RestController public class DemoController { @GetMapping("/admin/hello") public String admin() { return "hello admin"; } @GetMapping("/user/hello") public String user() { return "hello user"; } } 复制代码
添加 Basic Auth
填写请求参数
访问资源时,请求头带上AccessToken即可
有个问题需要特殊说明:
如果在spring security oauth2中,授权服务使用redis存储token的时候,报错: java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V 这说明版本有问题,解决方案是,将oauth2的版本升级到2.3.3
引入redis依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- 指明版本,解决redis存储出现的问题:java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V问题 --> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> 复制代码
授权服务器配置修改
/** * 功能描述: 授权服务器配置 * @author Trazen * @date 2020/7/8 22:26 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { /** * 该对象用来支持 password 模式 */ @Autowired private AuthenticationManager authenticationManager; /** * 该对象用来将令牌信息存储到内存中 * OAuth2令牌持久化主要有以下几种 * InMemoryTokenStore 内存存储 OAuth2默认储存方式 * JdbcTokenStore 数据库存储 * RedisTokenStore Redis存储 * JwkTokenStore & JwtTokenStore */ @Autowired(required = false) private TokenStore inMemoryTokenStore; /** * 该对象将为刷新token提供支持 * * InMemoryUserDetailsManager:从内存中加载用户账号 默认 * JdbcUserDetailsManager:从数据库中加载用户账号 */ @Autowired private UserDetailsService userDetailsService; /** * 该对象用来将令牌信息存储到Redis中 */ @Autowired private RedisConnectionFactory redisConnectionFactory; /** * 指定密码的加密方式 * 推荐使用BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder等 * @return */ @Bean PasswordEncoder passwordEncoder() { // 使用BCrypt强哈希函数加密方案(密钥迭代次数默认为10) return new BCryptPasswordEncoder(); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { // 表示支持 client_id 和 client_secret 做登录认证 security.allowFormAuthenticationForClients(); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("app") //授权模式为password和refresh_token两种 .authorizedGrantTypes("password", "refresh_token") // 配置access_token的过期时间 .accessTokenValiditySeconds(1800) //配置资源id .resourceIds("resId") .scopes("all") //123456加密后的密码 .secret("$2a$10$tnj.nZjSzCBckTh2fRRK9.ZTYfU0y4pDiZZChKxxeOElBsxaQCn26"); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //配置令牌的存储(这里存放在内存中) // endpoints.tokenStore(inMemoryTokenStore) // .authenticationManager(authenticationManager) // .userDetailsService(userDetailsService); //配置令牌存放在Redis中 endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory)) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } } 复制代码
测试 通过请求获取AccessToken后,在Redis中看是否有相关数据
引入jwt相关依赖:
<!-- jwt --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> <version>1.1.1.RELEASE</version> </dependency> 复制代码
增加Jwt存储token的配置类
/** * 功能描述: 使用Jwt存储token的配置 * @author Trazen * @date 2020/7/10 15:00 */ @Configuration public class JwtTokenStoreConfig { @Bean public TokenStore jwtTokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); //配置JWT使用的秘钥 accessTokenConverter.setSigningKey("test_key"); return accessTokenConverter; } } 复制代码
授权服务器配置修改
/** * 功能描述: 授权服务器配置 * @author Trazen * @date 2020/7/8 22:26 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { /** * 该对象用来支持 password 模式 */ @Autowired private AuthenticationManager authenticationManager; /*****************InMemoryTokenStore引入 ****************************/ /** * * 该对象用来将令牌信息存储到内存中 * OAuth2令牌持久化主要有以下几种 * InMemoryTokenStore 内存存储 OAuth2默认储存方式 * JdbcTokenStore 数据库存储 * RedisTokenStore Redis存储 * JwkTokenStore & JwtTokenStore */ @Autowired(required = false) private TokenStore inMemoryTokenStore; /** * 该对象将为刷新token提供支持 * * InMemoryUserDetailsManager:从内存中加载用户账号 默认 * JdbcUserDetailsManager:从数据库中加载用户账号 */ @Autowired private UserDetailsService userDetailsService; /*****************redisTokenStore引入 ****************************/ /** * 使用Redis存储时添加 * 该对象用来将令牌信息存储到Redis中 */ @Autowired private RedisConnectionFactory redisConnectionFactory; /*****************jwtTokenStore引入 ****************************/ @Autowired @Qualifier("jwtTokenStore") private TokenStore tokenStore; @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; /** * 指定密码的加密方式 * 推荐使用BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder等 * @return */ @Bean PasswordEncoder passwordEncoder() { // 使用BCrypt强哈希函数加密方案(密钥迭代次数默认为10) return new BCryptPasswordEncoder(); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { // 表示支持 client_id 和 client_secret 做登录认证 security.allowFormAuthenticationForClients(); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("app") //授权模式为password和refresh_token两种 .authorizedGrantTypes("password", "refresh_token") // 配置access_token的过期时间 .accessTokenValiditySeconds(1800) //配置资源id .resourceIds("resId") .scopes("all") //123456加密后的密码 .secret("$2a$10$tnj.nZjSzCBckTh2fRRK9.ZTYfU0y4pDiZZChKxxeOElBsxaQCn26"); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //配置令牌的存储(这里存放在内存中) // endpoints.tokenStore(inMemoryTokenStore) // .authenticationManager(authenticationManager) // .userDetailsService(userDetailsService); //配置令牌存放在Redis中 // endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory)) // .authenticationManager(authenticationManager) // .userDetailsService(userDetailsService); //配置使用jwt存储token endpoints.tokenStore(tokenStore) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .accessTokenConverter(jwtAccessTokenConverter); } } 复制代码
通过请求获取AccessToken,查看是否生效
通常通过令牌端点请求返回如下图所示,但是在实际操作中,我们往往需要在这个基础上,定制自己的返回信息,这就需要我们对这个东西进行自定义,下面就演示如何通过TokenEnhancer自定义返回信息
1.自定义一个TokenEnhancer类,添加需要定制返回的信息,代码如下
/** * 功能描述: Token增强 * @author Trazen * @date 2020/7/10 16:29 */ @Component public class HxTokenEnhancer implements TokenEnhancer { private final static String CLIENT_CREDENTIALS = "client_credentials"; @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { /** * 客户端模式不修改 */ if (CLIENT_CREDENTIALS.equals(authentication.getOAuth2Request().getGrantType())) { return accessToken; } final Map<String, Object> additionalInfo = new HashMap<>(8); Map<String, Object> info = new LinkedHashMap<>(); info.put("author", "Trazen"); info.put("email", "trazen@126.com"); info.put("GitHub", "https://github.com/ty1972873004/spring-security-oauth2-demo"); info.put("user", SecurityContextHolder.getContext().getAuthentication().getPrincipal()); additionalInfo.put("info", info); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } } 复制代码
修改授权服务器配置
/** * 功能描述: 授权服务器配置 * @author Trazen * @date 2020/7/8 22:26 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { /** * 该对象用来支持 password 模式 */ @Autowired private AuthenticationManager authenticationManager; /*****************InMemoryTokenStore引入 ****************************/ /** * * 该对象用来将令牌信息存储到内存中 * OAuth2令牌持久化主要有以下几种 * InMemoryTokenStore 内存存储 OAuth2默认储存方式 * JdbcTokenStore 数据库存储 * RedisTokenStore Redis存储 * JwkTokenStore & JwtTokenStore */ @Autowired(required = false) private TokenStore inMemoryTokenStore; /** * 该对象将为刷新token提供支持 * * InMemoryUserDetailsManager:从内存中加载用户账号 默认 * JdbcUserDetailsManager:从数据库中加载用户账号 */ @Autowired private UserDetailsService userDetailsService; /*****************redisTokenStore引入 ****************************/ /** * 使用Redis存储时添加 * 该对象用来将令牌信息存储到Redis中 */ @Autowired private RedisConnectionFactory redisConnectionFactory; /*****************jwtTokenStore引入 ****************************/ @Autowired @Qualifier("jwtTokenStore") private TokenStore tokenStore; @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Autowired private HxTokenEnhancer tokenEnhancer; /** * 指定密码的加密方式 * 推荐使用BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder等 * @return */ @Bean PasswordEncoder passwordEncoder() { // 使用BCrypt强哈希函数加密方案(密钥迭代次数默认为10) return new BCryptPasswordEncoder(); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { // 表示支持 client_id 和 client_secret 做登录认证 security.allowFormAuthenticationForClients(); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("app") //授权模式为password和refresh_token两种 .authorizedGrantTypes("password", "refresh_token") // 配置access_token的过期时间 .accessTokenValiditySeconds(1800) //配置资源id .resourceIds("resId") .scopes("all") //123456加密后的密码 .secret("$2a$10$tnj.nZjSzCBckTh2fRRK9.ZTYfU0y4pDiZZChKxxeOElBsxaQCn26"); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //配置令牌的存储(这里存放在内存中) // endpoints.tokenStore(inMemoryTokenStore) // .authenticationManager(authenticationManager) // .userDetailsService(userDetailsService); //配置令牌存放在Redis中 // endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory)) // .authenticationManager(authenticationManager) // .userDetailsService(userDetailsService); //配置使用jwt存储token //添加自定义Token信息配置 TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); List<TokenEnhancer> enhancerList = new ArrayList(); enhancerList.add(jwtAccessTokenConverter); enhancerList.add(tokenEnhancer); tokenEnhancerChain.setTokenEnhancers(enhancerList); endpoints.tokenStore(tokenStore) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .tokenEnhancer(tokenEnhancerChain) .accessTokenConverter(jwtAccessTokenConverter); } } 复制代码
测试如下: