OAuth 2四种模式
- 授权码(authorization-code)
- 隐藏式(implicit)
- 密码式(password):
- 客户端凭证(client credentials)
1.授权码模式
应用场景:第三方账号登录,接入微信开放平台,需要申请appid ,appsecret相当于clientId,secret。
1.1操作步骤:
B网站的用户可以登录 A网站,A网站可以在用户同意授权以后去B网站拿一些开放的数据
弹出B网站登录二维码网址:
http://B.com/oauth/authorize?response_type=code&client_id=client1&redirect_uri=https://www.baidu.com
请求access_token网址:
获取token样式:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoiaGVsbG8iLCJzY29wZSI6WyJhbGwiXSwiZGV0YWlscyI6eyJyZW1vdGVBZGRyZXNzIjoiMDowOjA6MDowOjA6MDoxIiwic2Vzc2lvbklkIjoiQjZGNTZBNTEwRUUzMTUwOTc0RjZDN0Q3NzgwMEJDQTkifSwiZXhwIjoxNjMxMDk0MDU1LCJ1c2VyTmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlt7ImF1dGhvcml0eSI6IlVTRVIifV0sImp0aSI6IjA1ZWM3ZGJlLTk3NWUtNGMzMS1hYzA2LWI0NTZkNDM3ZDBhNyIsImNsaWVudF9pZCI6ImNsaWVudDEifQ.F4PmzFOUnyXjAsXKzCS8upagfH7q2_KVHcdOkxXHA1eBNdGi5llItUDeI2Iug0waPWh1wsM7FdusM9jWL5lbsqdWR0atXzlRX8OIldCvJl4PdK9b2YMhBFqcZyG5ch-ZdtCMC9JPymVx5wrVT0hFDqwSadrdJhUC6FHFlso3U_AmdmpLzpfFgvSJAxB-C-fmpYGeR2UTxHPMC1IEewNbx00Q05adSUSIi2Qs5zMtl5T5UYdVlWJyaDYciEAjGaqd4US_fQi-vkIYKMn9Rj3IzK_TCUfsxjGg1Z5pCpO-loVJ5ooABixrg_F6IOXYlSYhAZLpeV9zcyVEzRpIiqzRPA",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoiaGVsbG8iLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiMDVlYzdkYmUtOTc1ZS00YzMxLWFjMDYtYjQ1NmQ0MzdkMGE3IiwiZGV0YWlscyI6eyJyZW1vdGVBZGRyZXNzIjoiMDowOjA6MDowOjA6MDoxIiwic2Vzc2lvbklkIjoiQjZGNTZBNTEwRUUzMTUwOTc0RjZDN0Q3NzgwMEJDQTkifSwiZXhwIjoxNjMxMDk5MDU1LCJ1c2VyTmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlt7ImF1dGhvcml0eSI6IlVTRVIifV0sImp0aSI6ImMxYzJhMTI4LWRiYzAtNGQ4Yi1iZTM4LWQzMjE1YWI4NmExMiIsImNsaWVudF9pZCI6ImNsaWVudDEifQ.YaS2h-jb9gJWsc6ftMkQB9cbPfusrZsQgTielWVwODkNHfiuUfL-ORrlDv5DNkhaeLk-DIDD6y-rpZgJKj2dS_ht_tIMelAlKgxWWjDKYhlY4GRxF9WjqZmma1IXkbLbrpBktu_Vc2JuexG0o9QFXOHyMKfZ6ouwZPHsw_eGN-XgJuhdOByizITEExCs9EDw5kGi6ph1ZCrx5NQvHcLzoPt0iItDY3CvoYLURN61JgdJoY5EonHuQi0ZiMUl8daSvu99-2Cm-Fvd_jsRarHKNWgvtdOmrAwgzkYvLipetT60zrkNGwkyFCW6_uLSYVCm92m9qtxpq42VjY6tVqx4kA",
"expires_in": 3599,
"scope": "all",
"userName": "admin",
"authorities": [
{
"authority": "USER"
}
],
"jti": "05ec7dbe-975e-4c31-ac06-b456d437d0a7"
}
1.2 代码实现:
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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-secret-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-secret-demo</name>
<description>spring-secret-demo</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.3</spring-cloud.version>
</properties>
<dependencies>
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
授权服务类:
package com.example.springsecretdemo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
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 java.util.Arrays;
import java.util.concurrent.TimeUnit;
/**
* 授权服务器配置
**/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
// @Autowired
// private PasswordEncoder passwordEncoder;
@Autowired
UserDetailsService userDetailsService;
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// converter.setSigningKey("123");
KeyStoreKeyFactory keyStoreKeyFactory =
new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
converter.setAccessTokenConverter(new CustomerAccessTokenConverter());
return converter;
}
/**
* 注入自定义token生成方式
*
* @return
*/
@Bean
public TokenEnhancer customerEnhancer() {
return new CustomTokenEnhancer();
}
/**
* 配置客户端详情服务
* 客户端详细信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//用于标识用户ID
.withClient("client1")
//授权方式
.authorizedGrantTypes("authorization_code","implicit","password","client_credentials ", "refresh_token")
//授权范围"test"
.scopes("all")
.redirectUris("https://www.baidu.com")
.accessTokenValiditySeconds(3600)
.refreshTokenValiditySeconds(8600)
//客户端安全码,secret密码配置从 Spring Security 5.0开始必须以 {bcrypt}+加密后的密码 这种格式填写;
.secret(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("123456"));
}
/**
* 用来配置令牌端点(Token Endpoint)的安全约束.
*
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
/* 配置token获取合验证时的策略 */
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()").allowFormAuthenticationForClients();
}
/**
* 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 配置tokenStore
// endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore())
// .accessTokenConverter(accessTokenConverter()).userDetailsService(userDetailsService);
//指定认证管理器
endpoints.authenticationManager(authenticationManager)
//refresh_token是否重复使用,false不允许
.reuseRefreshTokens(false);
//指定token存储位置
endpoints.tokenStore(tokenStore());
endpoints.accessTokenConverter(accessTokenConverter());
endpoints.userDetailsService(userDetailsService);
//自定义token生成方式
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customerEnhancer(), accessTokenConverter()));
endpoints.tokenEnhancer(tokenEnhancerChain);
// 配置TokenServices参数
DefaultTokenServices tokenServices = (DefaultTokenServices) endpoints.getDefaultAuthorizationServerTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1));//一天
endpoints.tokenServices(tokenServices);
//设置允许请求的方式
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);
}
}
自定义token生成类
package com.example.springsecretdemo.config;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 自定义CustomerAccessTokenConverter 这个类的作用主要用于AccessToken的转换,
* 默认使用DefaultAccessTokenConverter 这个装换器
* DefaultAccessTokenConverter有个UserAuthenticationConverter,这个转换器作用是把用户的信息放入token中,
* 默认只是放入user_name
**/
public class CustomerAccessTokenConverter extends DefaultAccessTokenConverter {
public CustomerAccessTokenConverter() {
super.setUserTokenConverter(new CustomerUserAuthenticationConverter());
}
private class CustomerUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
@Override
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
LinkedHashMap<String, Object> response = new LinkedHashMap<>();
response.put("details", authentication.getDetails());
response.put("test", "hello");
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
}
}
package com.example.springsecretdemo.config;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import java.util.HashMap;
import java.util.Map;
/**
* 自定义token生成携带的信息
**/
public class CustomTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
final Map<String, Object> additionalInfo = new HashMap<>();
//获取登录信息
UserDetails user = (UserDetails) oAuth2Authentication.getUserAuthentication().getPrincipal();
additionalInfo.put("userName", user.getUsername());
additionalInfo.put("authorities", user.getAuthorities());
((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(additionalInfo);
return oAuth2AccessToken;
}
}
package com.example.springsecretdemo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
/**
* 配置spring security
**/
@EnableWebSecurity//开启权限验证
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 配置这个bean会在做AuthorizationServerConfigurer配置的时候使用
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 配置用户
* 使用内存中的用户,实际项目中,一般使用的是数据库保存用户,具体的实现类可以使用JdbcDaoImpl或者JdbcUserDetailsManager
*
* @return
*/
@Bean
@Override
protected UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("admin").password(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("admin")).authorities("USER").build());
return manager;
}
// @Override
// protected void configure(HttpSecurity http) throws Exception {
// http.requestMatchers().anyRequest().and().authorizeRequests().antMatchers("/oauth/*").permitAll();
// }
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().permitAll().and().authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.antMatchers("/actuator/**").permitAll()
.anyRequest().authenticated()
.and().logout().permitAll()
.and().csrf().disable();
}
}
package com.example.springsecretdemo.dao;
import com.example.springsecretdemo.entity.User;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public User getById(String username) {
return new User(username,"user");
}
}
代码地址:
https://gitee.com/itwangfl/spring-boot-demo.git
访问资源地址:
密码方式获取token:
code方式获取token:
获取code
获取到的:https://www.baidu.com/?code=435zh7
获取token
http://localhost:8001/oauth/token?grant_type=authorization_code&code=435zh7&client_id=client1&client_secret=123456&redirect_uri=https://www.baidu.com&scope=all