新建两个服务
1.授权服务 端口号:11007
2.资源服务 端口号:11004
资源服务可以是订单服务、用户服务、商品服务等等
当然这两个服务也可以合并到一起, 依次顺序AuthorizationServerConfiguration、ResourceServerConfig、WebSecurityConfiguration;
WebSecurityConfiguration 会覆盖ResourceServerConfig的部分配置,
例如 authorizeRequests().antMatchers().
sql: https://github.com/spring-attic/spring-security-oauth/blob/main/spring-security-oauth2/src/test/resources/schema.sql
在mysql执行报错时,注意字段为LONGVARBINARY
类型,对应mysql的blob
类型
表明 | 说明 |
---|---|
oauth_client_details |
客户端账号密码、授权、回调地址等重要信息; 手动插入数据 |
oauth_access_token |
存储access_token。 授权成功后自动写入数据,过期后不会删除,但再次获取授权会覆盖 |
oauth_refresh_token |
存储refresh_token。授权成功后自动写入数据,过期后不会删除,但再次获取授权会覆盖 |
oauth_code |
存储授权码。authorization_code类型的授权code |
oauth_approvals |
存储授权成功的客户端信息。 |
存储从服务端获取的token数据。 JdbcClientTokenServices 已经被标记为@Deprecated ,估计已经弃用 | |
自定义oauth_client_details表的详情表 |
oauth_client_token、ClientDetails 暂时无用、可以不用创建
insert一条oauth_client_details数据
INSERT INTO `oauth_client_details` VALUES
(
'inside001',
NULL,
'$2a$10$FCaIYtevbAi5HCXF5PeSVO7zFQgyP7XbPF0zXip7FEL1UrBoE2PyK',
'read',
'client_credentials,authorization_code,password,refresh_token',
'http://www.baidu.com',
NULL, NULL, NULL, NULL, NULL
);
implementation(‘org.springframework.cloud:spring-cloud-starter-oauth2:3.0.0-RC1’)
package com.xxxx.oauth.service.impl;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class CustomerUserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password = new BCryptPasswordEncoder().encode("1");
return new User(username, password, AuthorityUtils.createAuthorityList("test"));
}
}
必须继承org.springframework.security.core.userdetails.UserDetailsService
对于loadUserByUsername
的实现, 真实业务可能需要验证username,验证roles等等, 现在直接返回不验证
package com.xxxx.oauth.oauth2;
import com.xxxx.oauth.service.impl.CustomerUserDetailsServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
import java.util.concurrent.TimeUnit;
/**
* 授权服务配置
*
* @author Administrator
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
/**
* 存储令牌,采用数据库
* @return
*/
@Bean
public TokenStore JdbcTokenStore() {
return new JdbcTokenStore(dataSource);
}
/**
* 授权码,采用数据库
* @return
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
/**
* 授权信息,采用数据库
* @return
*/
@Bean
public ApprovalStore jdbcApprovalStore(){
return new JdbcApprovalStore(dataSource);
}
/**
* 客户端client服务,采用数据库,且设置加密方式
* @return
*/
@Bean
public ClientDetailsService jdbcClientDetailsService(){
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
jdbcClientDetailsService.setPasswordEncoder(passwordEncoder());
return jdbcClientDetailsService;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 自定义 实现用户类
*/
@Autowired
private CustomerUserDetailsServiceImpl customerUserDetailsServiceImpl;
/**
* 配置端点
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
/******令牌服务配置*********************************************************************/
DefaultTokenServices tokenServices = new DefaultTokenServices();
// 存储令牌
tokenServices.setTokenStore(JdbcTokenStore());
// 是否允许刷新令牌
tokenServices.setSupportRefreshToken(true);
// 是否重复使用刷新令牌(直到过期)
tokenServices.setReuseRefreshToken(true);
// 客户端client服务
tokenServices.setClientDetailsService(jdbcClientDetailsService());
// 用来控制令牌存储增强策略
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
// AccessToken有效时间,优先取数据库中的配置,如果未配置,此处才会生效
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(7));
// RefreshToken有效时间,优先取数据库中的配置,如果未配置,此处才会生效
tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30));
/******端点配置*********************************************************************/
//认证管理器
endpoints.authenticationManager(authenticationManager);
//用户数据
endpoints.userDetailsService(customerUserDetailsServiceImpl);
//令牌存储
endpoints.tokenStore(JdbcTokenStore());
//令牌端点的请求方式, 默认是POST
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);
//令牌服务
endpoints.tokenServices(tokenServices);
//authorization_code类型的授权,code写入DB, 不配置此项则默认内存
endpoints.authorizationCodeServices(authorizationCodeServices());
endpoints.approvalStore(jdbcApprovalStore());
}
/**
* 配置客户端client
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//client服务
clients.withClientDetails(jdbcClientDetailsService());
}
/**
* 安全约束
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
//允许客户端使用表单方式发送请求token的认证
//如果未设置,则需要再http的Authorization中设置授权
.allowFormAuthenticationForClients()
// 接口/oauth/token_key的权限 默认拒绝所有 denyAll() 已通过身份验证 isAuthenticated() permitAll() 公开的
.tokenKeyAccess("isAuthenticated()")
// 接口/oauth/check_token的权限 默认拒绝所有 denyAll() 已通过身份验证 isAuthenticated() permitAll() 公开的
.checkTokenAccess("isAuthenticated()");
}
}
package com.xxxx.oauth.oauth2;
import com.xxxx.oauth.service.impl.CustomerUserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @author Administrator
/*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
/**
* 自定义 实现用户类
*/
@Autowired
public CustomerUserDetailsServiceImpl customerUserDetailsServiceImpl;
/**
* 重新实例化 AuthenticationManager Bean
*/
@Override
@Bean()
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 授权管理配置
*
* @param authManager
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder authManager) throws Exception {
authManager.userDetailsService(customerUserDetailsServiceImpl);
}
/**
* 安全约束配置
*
* @param webSecurity
* @throws Exception
*/
@Override
public void configure(WebSecurity webSecurity) throws Exception {
//解决静态资源被拦截的问题
//web.ignoring().antMatchers("/favicon.ico", "/asserts/**");
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.userDetailsService(customerUserDetailsServiceImpl).formLogin().permitAll()
.and().authorizeRequests()
// 自定义的一些接口,可匿名访问
.antMatchers("/test/**", "/test1/**").permitAll()
// 静态文件,可匿名访问
.antMatchers( "/**/*.css", "/**/*.js").permitAll()
//排除上面可匿名访问外,其他全部需要鉴权认证
.anyRequest().authenticated()
// 关闭跨域保护;
.and().csrf().disable();
}
}
/oauth/authorize:授权端点 ,固定授权入口路径 ,也是授权服务器的用户允许授权的页面
/oauth/token :获取令牌端点
/oauth/confirm_access:用户确认授权提交端点
/oauth/error:授权服务错误信息端点
/oauth/check_token:用于资源服务访问的令牌解析端点
/oauth/token_key:提供公有密钥的端点,如果使用 JWT 令牌的话
implementation(‘org.springframework.cloud:spring-cloud-starter-oauth2:3.0.0-RC1’)
package com.xxxx.order.oauth2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
/**
* @author Administrator
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.formLogin().permitAll()
.and().authorizeRequests()
// 自定义的一些接口,可匿名访问
.antMatchers("/hello/sayHello", "/test/**").permitAll()
//排除上面可匿名访问外,其他全部需要鉴权认证
.anyRequest().authenticated()
// 关闭跨域保护;
.and().csrf().disable();
}
/**
* 资源服务令牌解析服务 配置远程ResourceServerTokenServices后,可不用设置yml远程security.oauth2配置
*
* @return
*/
@Bean
public ResourceServerTokenServices tokenService() {
//使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
RemoteTokenServices service=new RemoteTokenServices();
service.setCheckTokenEndpointUrl("http://localhost:11007/oauth/check_token");
service.setClientId("inside001");
service.setClientSecret("1");
return service;
}
}
浏览器请求授权码:http://localhost:11007/oauth/authorize?client_id=inside001&response_type=code&redirect_uri=http://www.baidu.com
client_id和redirect_uri的参数必须auth_client_details表中插入的数据
登录授权完成后浏览器会重定向到:https://www.baidu.com/?code=R0RtrN
此时R0RtrN为授权码
post请求地址: http://localhost:11007/oauth/token
body中参数 x-wwww-form-urlencoded
grant_type:authorization_code
code:R0RtrN 上一步获取的code
client_id:inside001
client_secret:1
redirect_uri:http://www.baidu.com
响应结果:
{
"access_token": "b9d0bf39-47d2-43bd-ade4-abf4fdf8cd73",
"token_type": "bearer",
"refresh_token": "e0898a7f-f2d5-4d91-81b4-9e259ede88a9",
"expires_in": 3599,
"scope": "read"
}
post请求地址: http://localhost:11007/oauth/token
请求body:
body中参数 x-wwww-form-urlencoded
grant_type:password
client_id:inside001
client_secret:1
username:inside001
password:1
username就是自定义用户详情类CustomerUserDetailsServiceImpl.loadUserByUsername验证处理,目前未验证,可随便输入
响应结果:
{
"access_token": "b9d0bf39-47d2-43bd-ade4-abf4fdf8cd73",
"token_type": "bearer",
"refresh_token": "e0898a7f-f2d5-4d91-81b4-9e259ede88a9",
"expires_in": 3599,
"scope": "read"
}
因为在资源服务的ResourceServerConfig配置中,configure(HttpSecurity http)方法下,.antMatchers(“/hello/sayHello”, “/test/**”).permitAll() 配置了
接口直接请求, 例如:http://localhost:11004/hello/sayHello
请求:csshttp://localhost:11004/hello/passwordEncoder?pwd=1
在请求header中添加access_token:Authorization:Bearer 33453e78-cc4b-4ca8-8d5d-bbfd719a1f8b
正常情况下,access_token有效时间小于refresh_token有效时间,access_token失效后可利用refresh_token重新获取access_token, 当refresh_token失效后就需要重新获取授权。
post请求地址: http://localhost:11007/oauth/token
body中参数 x-wwww-form-urlencoded
grant_type:refresh_token
refresh_token:0f941d67-da95-479c-bef4-435b5c823402
client_id:inside001
client_secret:1