OAuth授权方式
客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式。
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
授权码模式
它的步骤如下:
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
A步骤中,客户端申请认证的URI,包含以下参数:
- response_type:表示授权类型,必选项,此处的值固定为"code"
- client_id:表示客户端的ID,必选项
- redirect_uri:表示重定向URI,可选项
- scope:表示申请的权限范围,可选项
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
下面是一个例子:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
C步骤中,服务器回应客户端的URI,包含以下参数:
- code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
- state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
下面是一个例子:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:
- grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。
- code:表示上一步获得的授权码,必选项。
- redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
- client_id:表示客户端ID,必选项。
下面是一个例子:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
E步骤中,认证服务器发送的HTTP回复,包含以下参数:
- access_token:表示访问令牌,必选项。
- token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。
- expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
- refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。
- scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
下面是一个例子:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
springboot2.0+ 和OAuth认证服务器(自动配置)
1、在Springboot2.0+以后在自动配置中已经没有OAuth了,所以要启用认证服务器需要增加如下依赖
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
同时定义类,在类上增加@EnableAuthorizationServer注解,为什么这样具体可以看
OAuth2AutoConfiguration -> OAuth2AuthorizationServerConfiguration类
@Configuration
@EnableAuthorizationServer
public class SSOAuthenticationServerConfig {
}
2、实现拦截器(因为Springboot2.0+以后没有对/oauth/authorize路径拦截,)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
3、配置认证服务器配置
@Configuration
@EnableAuthorizationServer
public class SSOAuthenticationServerConfig {
@Bean
public BaseClientDetails clientDetail() {
BaseClientDetails clientDetail = new BaseClientDetails();
clientDetail.setClientId("gz");
clientDetail.setClientSecret("gz123");
clientDetail.setRegisteredRedirectUri(new HashSet<String>(Arrays.asList("http://localhost:8083/hello")));
clientDetail.setAuthorizedGrantTypes(Arrays.asList("authorization_code",
"password", "client_credentials", "implicit", "refresh_token"));
clientDetail.setAuthorities(
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
return clientDetail;
}
}
或者在配置文件中
security:
oauth2:
client:
client-id: gz
client-secret: gz123
registered-redirect-uri: http://localhost:8083/hello
grant-type: authorization_code,password,client_credentials,implicit,refresh_token
scope: all
注:自动配置只能配置单个clientId,不能配置多个,如果要配置多个请看(springboot2.0+ 和OAuth认证服务器(手动配置))
4、在浏览器中调用http://localhost:8083/oauth/authorize?response_type=code&client_id=gz&state=xyz&redirect_uri=http://localhost:8083/hello
完成步骤C获取Code信息
5、code获取token
5.1 输入url
5.2 生成basic auth,Username->clientId;Password->clientSecret
5.4 填写请求参数参数
响应结果
6.
7. 使用JWT生成token
@Configuration
@EnableAuthorizationServer
public class SSOAuthenticationServerConfig {
@Bean
public BaseClientDetails clientDetail() {
BaseClientDetails clientDetail = new BaseClientDetails();
clientDetail.setClientId("gz");
clientDetail.setClientSecret("gz123");
clientDetail.setRegisteredRedirectUri(new HashSet<String>(Arrays.asList("http://localhost:8083/hello")));
clientDetail.setAuthorizedGrantTypes(Arrays.asList("authorization_code",
"password", "client_credentials", "implicit", "refresh_token"));
clientDetail.setAuthorities(
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER1"));
clientDetail.setScope(Arrays.asList("all"));
return clientDetail;
}
// 只需要增加如下bean
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtConverter = new JwtAccessTokenConverter();
jwtConverter.setSigningKey("gz");
return jwtConverter;
}
}
springboot2.0+ 和OAuth认证服务器(手动配置)
参考springboot2.0+ 和OAuth认证服务器(自动配置) 将步骤3改成如下
@Configuration
@EnableAuthorizationServer
public class SSOAuthenticationServerConfig2 extends AuthorizationServerConfigurerAdapter{
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("gz")
.secret("gz123")
.authorizedGrantTypes("authorization_code",
"password", "client_credentials", "implicit", "refresh_token")
.redirectUris("http://localhost:8083/hello")
.scopes("all")
.and()
.withClient("gz1")
.secret("gz123")
.authorizedGrantTypes("authorization_code",
"password", "client_credentials", "implicit", "refresh_token")
.redirectUris("http://localhost:8084/hello")
.scopes("all");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.accessTokenConverter(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtConverter = new JwtAccessTokenConverter();
jwtConverter.setSigningKey("gz");
return jwtConverter;
}
@SuppressWarnings("deprecation")
@Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
// 必须配置,否则在BasicAuthenticationFilter认证时会报错
security.passwordEncoder(NoOpPasswordEncoder.getInstance());
}
}
注意:
- springboot2.0+必须配置ClientDetailsServiceConfigurer 属性及redirectUris,scopes,authorizedGrantTypes
- springboot2.0+ 和OAuth认证服务器(手动配置)中,要么在public void configure(AuthorizationServerSecurityConfigurer security)方法中注入不加密的密码解析器,要么做如下配置clients.inMemory().secret("{noop}gz123")增加{noop}
- springboot2.0+ 和OAuth认证服务器(自动配置)步骤1中和资源服务器整合时,不能用
http.formLogin()
, - springboot2.0+ 和OAuth认证服务器(手动配置)中利用@EnableOAuth2Sso,启用Jwt必须将其注册到Spring容器中,不能new
@Configuration
@EnableAuthorizationServer
public class SSOAuthenticationServerConfig2 extends AuthorizationServerConfigurerAdapter{
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("gz")
.secret("{noop}gz123")
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
.redirectUris("http://localhost:8083/hello")
.scopes("all")
.and()
.withClient("gz1")
.secret("{noop}gz123")// 重要配置
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
.redirectUris("http://localhost:8085/hello")
.scopes("all");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.accessTokenConverter(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtConverter = new JwtAccessTokenConverter();
jwtConverter.setSigningKey("gz");
return jwtConverter;
}
密码模式
采用密码模式,在禁用Springboot Oauth自动配置后,访问会报如下错误,是因为缺少authenticationManager
具体请看AuthorizationServerEndpointsConfigurer.getDefaultTokenGranters()方法
private List<TokenGranter> getDefaultTokenGranters() {
ClientDetailsService clientDetails = clientDetailsService();
AuthorizationServerTokenServices tokenServices = tokenServices();
AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
OAuth2RequestFactory requestFactory = requestFactory();
List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
requestFactory));
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
tokenGranters.add(implicit);
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
// 如果authenticationManager 不为空,则启用密码模式
if (authenticationManager != null) {
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
clientDetails, requestFactory));
}
return tokenGranters;
}
密码模式使用(手动配置)
注:密码模式自动配置不需要再定义AuthenticationManager
1、增加AuthenticationManager bean
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
// 重点
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
2、在AuthorizationServerEndpointsConfigurer 中增加AuthenticationManager
@Configuration
@EnableAuthorizationServer
public class SSOAuthenticationServerConfig extends AuthorizationServerConfigurerAdapter{
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("gz")
.secret("{noop}gz123")
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
.redirectUris("http://localhost:8084/hello")
.scopes("all");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 增加.authenticationManager(authenticationManager);
endpoints.accessTokenConverter(jwtAccessTokenConverter()).authenticationManager(authenticationManager);
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtConverter = new JwtAccessTokenConverter();
jwtConverter.setSigningKey("gz");
return jwtConverter;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
security.tokenKeyAccess("isAuthenticated()");
}
}
3、编写UserDetailsService类(必须配置)
@Component
public class ConstumUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User("gz","{noop}123456",true,true,true,true, AuthorityUtils.createAuthorityList("admin"));
}
}
4、修改application.yml配置文件
server:
port: 8083
logging:
level:
root: INFO
5、测试