Repository: copoile/springcloud-oauth2
Branch: master
Commit: 45f62fef2ddf
Files: 49
Total size: 89.8 KB
Directory structure:
gitextract_uukuw93h/
├── .gitignore
├── LICENSE
├── README.md
├── auth-server/
│ ├── .gitignore
│ ├── pom.xml
│ └── src/
│ ├── .gitignore
│ └── main/
│ ├── java/
│ │ └── cn/
│ │ └── poile/
│ │ └── ucs/
│ │ └── auth/
│ │ ├── AuthServerApplication.java
│ │ ├── Token/
│ │ │ └── MobileCodeAuthenticationToken.java
│ │ ├── config/
│ │ │ ├── AuthorizationConfig.java
│ │ │ ├── IgnoreLogoutFilter.java
│ │ │ ├── RedisAuthorizationCodeServices.java
│ │ │ ├── RedisConfig.java
│ │ │ ├── ResourceServerConfig.java
│ │ │ └── SecurityConfigurerAdapter.java
│ │ ├── constant/
│ │ │ └── RedisConstant.java
│ │ ├── controller/
│ │ │ └── AuthenticationController.java
│ │ ├── entity/
│ │ │ ├── SysAuthority.java
│ │ │ └── SysUser.java
│ │ ├── granter/
│ │ │ └── MobileCodeTokenGranter.java
│ │ ├── provider/
│ │ │ └── MobileCodeAuthenticationProvider.java
│ │ ├── service/
│ │ │ ├── ClientDetailsServiceImpl.java
│ │ │ ├── SysClientDetailService.java
│ │ │ ├── SysUserService.java
│ │ │ └── UserDetailsServiceImpl.java
│ │ └── vo/
│ │ └── UserDetailImpl.java
│ └── resources/
│ ├── application-dev.yml
│ ├── application.yml
│ ├── static/
│ │ └── css/
│ │ └── signin.css
│ └── templates/
│ └── ftl/
│ └── login.ftl
├── eureka-server/
│ ├── .gitignore
│ ├── pom.xml
│ └── src/
│ └── main/
│ ├── java/
│ │ └── cn/
│ │ └── poile/
│ │ └── ucs/
│ │ └── eureka/
│ │ ├── EurekaServerApplication.java
│ │ └── config/
│ │ └── WebSecurityConfig.java
│ └── resources/
│ ├── application-dev.yml
│ └── application.yml
├── pom.xml
├── resource-server/
│ ├── .gitignore
│ ├── pom.xml
│ ├── src/
│ │ └── main/
│ │ ├── java/
│ │ │ └── cn/
│ │ │ └── poile/
│ │ │ └── ucs/
│ │ │ └── resources/
│ │ │ ├── ResourceServerApplication.java
│ │ │ ├── config/
│ │ │ │ ├── CustomizePrincipalExtractor.java
│ │ │ │ └── ResourceServerConfig.java
│ │ │ └── controller/
│ │ │ └── TestRestController.java
│ │ └── resources/
│ │ ├── application-dev.yml
│ │ └── application.yml
│ └── target/
│ └── classes/
│ ├── application-dev.yml
│ └── application.yml
└── source_note/
├── OAuth2ClientAuthenticationProcessingFilter.java
├── RoleVoter.java
└── TokenEndpoint_source_note.java
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Created by .ignore support plugin (hsz.mobi)
### Java template
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
/.idea/
*.iml
/auth-server/target/
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019-present Yaohw
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# 简介
本项目基于spring-cloud-starter-oauth2搭建的认证中心和资源服务器的微服务项目,项目不仅仅简单的demo,项目的出发点在于实战应用。本项目为笔者花了不少时间和精力整理出来的,只需要稍微调整就可应用于实际项目当中,并且项目包含大量注释,不仅可以让你会用,也可让你了解到一些流程、一些原理上的东西。认证中心完成密码模式、授权码模式、刷新token模式、简化模式、以及自定义的手机号验证码模式。
国内Gitee:[https://gitee.com/copoile/springcloud-oauth2.git](https://gitee.com/copoile/springcloud-oauth2.git)
> 如果大家有什么疑问或不懂的地方可以[issue](https://github.com/copoile/springcloud-oauth2/issues/new) 里提问。
有什么说得不对或不合理的地方也欢迎指出。希望对你有所帮助呦~ ^_^
# 分支说明
目前分支有master和prod,master分支为快速上手,未配置数据库,内容偏教程流程理解,prod分支新建数据库相关表,并实现认证相关数据查询,同时实现统一异常处理,统一Api。
prod分支-用户名:admin/123456,客户端:web/123456
# 功能
```
- 密码模式
- 自定义手机验证码模式
- 授权码模式
- 简化模式
- 刷token模式
- 退出测试接口
- 简单授权页面
- 不需要accessToken测试接口
- 需要accessToken测试接口
- 需要特定权限测试接口
- scope测试接口
```
## 开发环境
- **JDK 1.8 +**
- **Maven 3.5 +**
- **IntelliJ IDEA ULTIMATE 2018.2 +** (*注意:建议使用 IDEA 开发,同时保证安装 `lombok` 插件,如果是eclipse也要确保安装了`lombok` 插件*)
- **Redis 3.0 +**
## 运行方式
1. `git clone https://github.com/copoile/springcloud-oauth2.git`
2. 使用 IDEA 打开 clone 下来的项目
3. 项目启动顺序: eureka-server > auth-server > resource-server
> 注意:auth-server依赖redis服务,记得先启动redis服务哦~
# 认证验证流程
这里简单做下密码模式的认证和accessToken验证流程,手机号模式跟这个类型,授权码模式和简化模式稍微有点不一样,授权码模式和简化模式都是先跳到认证中心的授权页面,授权成功后回调回调地址,并且携带参数code或accessToken。

## 认证中心核心代码
### AuthorizationConfig.java
```java
/**
* 认证配置
* @author: yaohw
* @create: 2019-09-30 16:12
**/
@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private ClientDetailsServiceImpl clientDetailsService;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 配置token存储,这个配置token存到redis中,还有一种常用的是JwkTokenStore
* jwt的缺点已发布令牌不可控
* @return
*/
@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
/**
* 配置授权码模式授权码服务(存授权码和删除授权码),不配置默认为内存模式
* @return
*/
@Primary
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new RedisAuthorizationCodeServices(redisConnectionFactory);
}
/**
* 配置客户端详情(根据客户的id查询客户端)
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 采用token转jwt,并添加一些自定义信息(token增强)
// 默认使用随机UUID生成的token
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(
Arrays.asList(jwtAccessTokenConverter(),tokenEnhancer()));
endpoints.tokenEnhancer(tokenEnhancerChain)
// 配置token存储,一般配置redis存储
.tokenStore(tokenStore())
// 配置认证管理器
.authenticationManager(authenticationManager)
// 配置用户详情server,密码模式必须
.userDetailsService(userDetailsService)
// 配置授权码模式授权码服务,不配置默认为内存模式
.authorizationCodeServices(authorizationCodeServices())
// 配置grant_type模式,如果不配置则默认使用密码模式、简化模式、验证码模式以及刷新token模式,如果配置了只使用配置中,默认配置失效
// 具体可以查询AuthorizationServerEndpointsConfigurer中的getDefaultTokenGranters方法
.tokenGranter(tokenGranter(endpoints));
// 配置TokenServices参数
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
// 是否支持刷新Token
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(true);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
// 设置accessToken和refreshToken的默认超时时间(如果clientDetails的为null就取默认的,如果clientDetails的不为null取clientDetails中的)
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(2));
tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30));
endpoints.tokenServices(tokenServices);
}
/**
* jwt格式封装token
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
// 设置jwt加解密秘钥,不设置会随机一个
jwtAccessTokenConverter.setSigningKey("yaohw");
return jwtAccessTokenConverter;
}
/**
* token增强,添加一些元信息
*
* @return TokenEnhancer
*/
@Bean
public TokenEnhancer tokenEnhancer() {
return (accessToken, authentication) -> {
final Map<String, Object> additionalInfo = new HashMap<>(2);
additionalInfo.put("license", "yaohw");
UserDetailImpl user = (UserDetailImpl) authentication.getUserAuthentication().getPrincipal();
if (user != null) {
additionalInfo.put("username", user.getUsername());
}
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
};
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.allowFormAuthenticationForClients()
.tokenKeyAccess("isAuthenticated()")
.checkTokenAccess("permitAll()");
}
/**
* 创建grant_type列表
* @param endpoints
* @return
*/
private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
List<TokenGranter> list = new ArrayList<>();
// 这里配置密码模式、刷新token模式、自定义手机号验证码模式、授权码模式、简化模式
list.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
list.add(new RefreshTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
list.add(new MobileCodeTokenGranter(authenticationManager,endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
list.add(new AuthorizationCodeTokenGranter(endpoints.getTokenServices(),endpoints.getAuthorizationCodeServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
list.add(new ImplicitTokenGranter(endpoints.getTokenServices(),endpoints.getClientDetailsService(),endpoints.getOAuth2RequestFactory()));
return new CompositeTokenGranter(list);
}
}
```
### SecurityConfigurerAdapter.java
```java
/**
* security web安全配置,spring-cloud-starter-oauth2依赖于security
* 默认情况下SecurityConfigurerAdapter执行比ResourceServerConfig先
* @author: yaohw
* @create: 2019-09-25 16:49
*/
@Configuration
@EnableWebSecurity
public class SecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 配置认证管理器
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 配置密码加密对象(解密时会用到PasswordEncoder的matches判断是否正确)
* 用户的password和客户端clientSecret用到,所以存的时候存该bean encode过的密码
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 这里是对认证管理器的添加配置
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(provider())
.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**","/static/**");
}
/**
* 安全请求配置,这里配置的是security的部分,这里配置全部通过,安全拦截在资源服务的配置文件中配置,
* 要不然访问未验证的接口将重定向到登录页面,前后端分离的情况下这样并不友好,无权访问接口返回相关错误信息即可
* @param http
* @return void
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().loginPage("/login")
.permitAll()
.and().authorizeRequests().anyRequest().permitAll()
.and().csrf().disable().cors();
}
/**
* 自定义手机验证码认证提供者
*
* @return
*/
@Bean
public MobileCodeAuthenticationProvider provider() {
MobileCodeAuthenticationProvider provider = new MobileCodeAuthenticationProvider();
provider.setStringRedisTemplate(stringRedisTemplate);
provider.setHideUserNotFoundExceptions(false);
provider.setUserDetailsService(userDetailsService);
return provider;
}
}
```
### ResourceServerConfig.java
```java
/**
* 资源服务配置
* @author: yaohw
* @create: 2019-10-08 10:04
**/
@Configuration
// 启用资源服务
@EnableResourceServer
// 启用方法级权限控制
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Log4j2
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "auth-server";
/**
* 配置资源接口安全,http.authorizeRequests()针对的所有url,但是由于登录页面url包含在其中,这么配置会进行token校验,校验不通过返回错误json,
* 而授权码模式获取code时需要重定向登录页面,重定向过程并不能携带token,所有不能用http.authorizeRequests(),
* 而是用requestMatchers().antMatchers(""),这里配置的是需要资源接口拦截的url数组
* @param http
* @return void
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http //配置需要保护的资源接口
.requestMatchers().antMatchers("/user","/test/need_token","/update","/logout","/test/need_admin","/test/scope")
.and().authorizeRequests().anyRequest().authenticated();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID).stateless(true);
}
}
```
## 资源服务配置文件
```yml
spring:
application:
name: resource-server
server:
port: 8003
#服务器发现注册配置
eureka:
client:
serviceUrl:
#配置服务中心(可配置多个,用逗号隔开)
defaultZone: http://admin:admin@localhost:9000/eureka/
##安全配置##
security:
oauth2:
resource:
id: resource-server
## user-info-uri和token-info-uri二选择即可
## 如果配置了user-info-uri,该资源服务器使用userInfoTokenServices远程调用认证中心接口,通过认证中心的OAuth2AuthenticationProcessingFilter完成验证工作,一般设置user-info-uri即可
user-info-uri: http://127.0.0.1:8001/user
prefer-token-info: false
## 该资源服务器使用RemoteTokenServices远程调用认证中心接口,注意一点就是如果使用token-info-uri那么就必须设置上clientId和clientSecret,通过CheckTokenEndpoint完成验证工作
#token-info-uri: http://127.0.0.1:8001/oauth/check_token
#client:
#client-secret: yaohw
#client-id: yaohw
```
## 部分源代码讲解
### 认证(获取token)TokenEndpoint.java
```java
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException(
"There is no client authentication. Try adding an appropriate authentication filter.");
}
// 根据当前请求获取到clientId
String clientId = getClientId(principal);
// 获取当前ClientDetailsService(就是我们在AuthorizationConfig中配置)然后根据clientId去数据库查询客户端详情
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
// 将请求参数封装成TokenRequest
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
// 请求的clientId与查出来的匹配
if (clientId != null && !clientId.equals("")) {
// Only validate the client details if a client authenticated during this
// request.
if (!clientId.equals(tokenRequest.getClientId())) {
// double check to make sure that the client ID in the token request is the same as that in the
// authenticated client
throw new InvalidClientException("Given client ID does not match authenticated client");
}
}
// 校验客户端范围
if (authenticatedClient != null) {
oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
}
if (!StringUtils.hasText(tokenRequest.getGrantType())) {
throw new InvalidRequestException("Missing grant type");
}
// 判断是否是简化模式(简化模式不是这个接口,走的是AuthorizationEndpoint类下的/oauth/authorize)
if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
}
// 判断是否授权码模式,如果是,清空返回,因为授权码模式在第一步获取code的时候就将client信息缓存起来的,后面检验的是从缓存取出来补充完整
if (isAuthCodeRequest(parameters)) {
// The scope was requested or determined during the authorization step
if (!tokenRequest.getScope().isEmpty()) {
logger.debug("Clearing scope of incoming token request");
tokenRequest.setScope(Collections.<String> emptySet());
}
}
// 是否刷新token模式
if (isRefreshTokenRequest(parameters)) {
// A refresh token has its own default scopes, so we should ignore any added by the factory here.
tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
}
// 这步是整个认证的关键,这里简单说下流程,首先她会根据当前请求的grantType找到对应的认证模式,
// 比如密码模式的ResourceOwnerPasswordTokenGranter,
// 然后对应的AbstractTokenGranter调用对应的grant方法,grant方法中又调用经过一系列调用,
// 在getOAuth2Authentication方法中生成对应的AbstractAuthenticationToken,比如UsernamePasswordAuthenticationToken,
// 然后认证管理器(就是我们在AuthorizationConfig中配置的AuthenticationManager)调用认证方法authenticationManager.authenticate(abstractAuthenticationToken)
// AbstractAuthenticationToken和AuthenticationProvider是存在一一对应的关系
// 比如UsernamePasswordAuthenticationToken和DaoAuthenticationProvider,
// authenticationManager.authenticate()会根据传入的
// AbstractAuthenticationToken找到对应的AuthenticationProvider,
// 真正认证逻辑通过AuthenticationProvider来完成的,比如密码模式的DaoAuthenticationProvider,
// 会去根据用户名查询出对应的用户,
// 然后校验用户密码是否匹配,用户是否锁定过期等
// 具体可查看DaoAuthenticationProvider和她继承的AbstractUserDetailsAuthenticationProvider
// 理清上面的思路后,我们就可以自定义grantType
// 就是定义一个继承AbstractTokenGranter的类重写getOAuth2Authentication方法
// 该方法里面会用到AbstractAuthenticationToken和AuthenticationProvider
// 我们再分别定义一个类分别继承对应的类即可(大概思路,具体查看代码)
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
}
//这个没什么好说的,就是http请求响应体封装
return getResponse(token);
}
```
### 验证token
了解过OAuth2的同学应该知道它有资源服务和认证中心服务,那么它怎么保护资源服务接口的呢?实际上不管认证中服务还是资源服务,当请求的接口需要安全校验时都会被OAuth2ClientAuthenticationProcessingFilter所拦截,只是拦截后做了不同的处理(取决于ResourceServerTokenServices的实例)。资源服务:拦截请求后会远程调用认证服务器的`http://127.0.0.1:8001/user`或`http://127.0.0.1:8001/oauth/check_token`,至于调用哪个取决于配置文件,如配置如下配置将远程调用`http://127.0.0.1:8001/user`(资源服务端我们也一般这么配置即可)
```yml
##安全配置##
security:
oauth2:
resource:
id: resource-server
user-info-uri: http://127.0.0.1:8001/user
prefer-token-info: false
```
#### (拦截token校验)OAuth2AuthenticationProcessingFilter.java
```java
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
ServletException {
final boolean debug = logger.isDebugEnabled();
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
try {
Authentication authentication = tokenExtractor.extract(request);
Authentication authResult = authenticationManager.authenticate(authentication);
....略
// 这步是校验token的关键,这里tokenServices是ResourceServerTokenServices实例,这里做怎么样的操作取决于注入的
// ResourceServerTokenServices实例
// 默认情况下ResourceServerTokenServices的实例是DefaultTokenServices
// 认证中心使用的就是DefaultTokenServices,这个类做的就是tokenStore.readAccessToken(accessTokenValue)
// 我们配置中心配置的tokenStore的是RedisTokenStore,所以实际上她做的就是从redis中读取出accessToken相关信息
<!-- 分割线 --->
// 上面说的DefaultTokenServices是认证中心token的处理,资源服务下:
// 如果配置文件中配置的user-info-uri则ResourceServerTokenServices注入的实例将是UserInfoTokenServices的实例
// 如果配置token-info-uri则ResourceServerTokenServices注入的实例将是RemoteTokenServices
// 如果两者都配置了,优先UserInfoTokenServices
// UserInfoTokenServices和RemoteTokenServices做的事都是远程调度认证中心相应的接口完成token的校验
// 两者主要区别在于RemoteTokenServices需要配置clientId和clientSecret
// RemoteTokenServices中有这么一句话:Null Client ID or Client Secret detected. Endpoint that requires authentication will reject // request with 401 error. // 具体请查看RemoteTokenServices和UserInfoTokenServices
// OAuth2AuthenticationManager.java
String token = (String) authentication.getPrincipal();
OAuth2Authentication auth = tokenServices.loadAuthentication(token);
}
}
```
## postman接口测试截图
### 客户端Basic请求头
这里两种方式都是一样的,eWFvaHc6eWFvaHc=其实就是yaohw:yaohw,经过base64加密了一下


### 密码模式

### 自定义手机号验证码模式
> 注意:需要在redis中设置一个缓存,String类型,key为sms:code:你的手机号,值为短信验证码


### 授权码模式
##### 授权码模式步骤一
授权码模式步骤一 会跳转到认证中心的授权页面,这里为方便展示参数才用postman,get请求,应在浏览器直接打开(带对应参数),授权成功后会回调回调地址,并且会携带code。

##### 授权码模式步骤二(授权页面授权)

### 授权码模式步骤三(获取code)

### 授权码模式步骤四(根据code获取token)

### 简化模式
与授权码模式类似,不过回调后携带的参数不是code,还是access_token,比授权码模式少了一步.
### 步骤一
简化模式步骤一会跳转到认证中心的授权页面,这里为方便展示参数才用postman,get请求,应在浏览器直接打开(带对应参数),授权成功后会回调回调地址,并且会携带accessToken。

##### 步骤二(授权页面授权)

### 步骤三

### 刷新token模式

# License
[MIT](./LICENSE)
Copyright (c) 2019-present Yaohw
================================================
FILE: auth-server/.gitignore
================================================
/target/
================================================
FILE: auth-server/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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud-oauth2</artifactId>
<groupId>cn.poile.ucs</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>auth-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--oauth2.0-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!--freemarker,页面渲染引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<!--连接池依赖包,lettuce连接池需要该配置-->
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.7.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
================================================
FILE: auth-server/src/.gitignore
================================================
/test/
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/AuthServerApplication.java
================================================
package cn.poile.ucs.auth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* 认证中心服务
* @author: yaohw
* @create: 2019-09-25 16:48
**/
@SpringBootApplication
@EnableDiscoveryClient
@ServletComponentScan
public class AuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class,args);
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/Token/MobileCodeAuthenticationToken.java
================================================
package cn.poile.ucs.auth.Token;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* 手机号短信认证Token
* @author: yaohw
* @create: 2019-09-29 19:56
**/
public class MobileCodeAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;
private Object credentials;
public MobileCodeAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public MobileCodeAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
} else {
super.setAuthenticated(false);
}
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/config/AuthorizationConfig.java
================================================
package cn.poile.ucs.auth.config;
import cn.poile.ucs.auth.granter.MobileCodeTokenGranter;
import cn.poile.ucs.auth.service.ClientDetailsServiceImpl;
import cn.poile.ucs.auth.service.UserDetailsServiceImpl;
import cn.poile.ucs.auth.vo.UserDetailImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
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.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
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.redis.RedisTokenStore;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 认证配置
* @author: yaohw
* @create: 2019-09-30 16:12
**/
@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private ClientDetailsServiceImpl clientDetailsService;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 配置token存储,这个配置token存到redis中
* @return
*/
@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
/**
* 配置授权码模式授权码服务,不配置默认为内存模式
* @return
*/
@Primary
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new RedisAuthorizationCodeServices(redisConnectionFactory);
}
/**
* 配置客户端详情
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 采用token转jwt,并添加一些自定义信息(token增强)(有默认非必须)
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(
Arrays.asList(jwtAccessTokenConverter(),tokenEnhancer()));
endpoints.tokenEnhancer(tokenEnhancerChain)
// 配置token存储,一般配置redis存储
.tokenStore(tokenStore())
// 配置认证管理器
.authenticationManager(authenticationManager)
// 配置用户详情server,密码模式必须
.userDetailsService(userDetailsService)
// 配置授权码模式授权码服务,不配置默认为内存模式
.authorizationCodeServices(authorizationCodeServices())
// 配置grant_type模式,如果不配置则默认使用密码模式、简化模式、验证码模式以及刷新token模式,如果配置了只使用配置中,默认配置失效
// 具体可以查询AuthorizationServerEndpointsConfigurer中的getDefaultTokenGranters方法
.tokenGranter(tokenGranter(endpoints));
// 配置TokenServices参数
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
// 是否支持刷新Token
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(true);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
// 设置accessToken和refreshToken的默认超时时间(如果clientDetails的为null就取默认的,如果clientDetails的不为null取clientDetails中的)
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(2));
tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30));
endpoints.tokenServices(tokenServices);
}
/**
* jwt格式封装token
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
// 设置jwt加解密秘钥,不设置会随机一个
jwtAccessTokenConverter.setSigningKey("yaohw");
return jwtAccessTokenConverter;
}
/**
* token增强,添加一些元信息
*
* @return TokenEnhancer
*/
@Bean
public TokenEnhancer tokenEnhancer() {
return (accessToken, authentication) -> {
final Map<String, Object> additionalInfo = new HashMap<>(2);
additionalInfo.put("license", "yaohw");
UserDetailImpl user = (UserDetailImpl) authentication.getUserAuthentication().getPrincipal();
if (user != null) {
additionalInfo.put("username", user.getUsername());
}
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
};
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.allowFormAuthenticationForClients()
.tokenKeyAccess("isAuthenticated()")
.checkTokenAccess("permitAll()");
}
/**
* 创建grant_type列表
* @param endpoints
* @return
*/
private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
List<TokenGranter> list = new ArrayList<>();
// 这里配置密码模式、刷新token模式、自定义手机号验证码模式、授权码模式、简化模式
list.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
list.add(new RefreshTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
list.add(new MobileCodeTokenGranter(authenticationManager,endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
list.add(new AuthorizationCodeTokenGranter(endpoints.getTokenServices(),endpoints.getAuthorizationCodeServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
list.add(new ImplicitTokenGranter(endpoints.getTokenServices(),endpoints.getClientDetailsService(),endpoints.getOAuth2RequestFactory()));
return new CompositeTokenGranter(list);
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/config/IgnoreLogoutFilter.java
================================================
package cn.poile.ucs.auth.config;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* LogoutFilter过滤器会对/logout路径进行过滤
* 这里直接转发到remove请求下
* @author: yaohw
* @create: 2020/8/8 8:58 下午
*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class IgnoreLogoutFilter implements Filter {
private RequestMatcher requestMatcher;
public IgnoreLogoutFilter() {
this.setFilterProcessesUrl("/logout");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
if (requiresLogout(request,response)) {
RequestDispatcher requestDispatcher = request.getRequestDispatcher("remove");
requestDispatcher.forward(request,response);
} else {
filterChain.doFilter(request,response);
}
}
protected boolean requiresLogout(HttpServletRequest request, HttpServletResponse response) {
return this.requestMatcher.matches(request);
}
public void setFilterProcessesUrl(String filterProcessesUrl) {
this.requestMatcher = new AntPathRequestMatcher(filterProcessesUrl);
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/config/RedisAuthorizationCodeServices.java
================================================
package cn.poile.ucs.auth.config;
/**
* 注意一点,这里存的redis的序列表用默认的JdkSerializationStrategy,跟RedisTokenStore类似
* 不能用json的,使用json时反序列成token的时候会报错,非要用json的需要同时修改token序列化方式
*/
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy;
/**
* redis授权码模式授权码服务-操作授权码生成、存储、删除
*
* @author: yaohw
* @create: 2019-10-10 18:21
**/
public class RedisAuthorizationCodeServices extends RandomValueAuthorizationCodeServices {
private static final String AUTHORIZATION_CODE = "authorization:code:";
/**
* 授权码有效时长
*/
private long expiration = 300L;
/**
* key 前缀
*/
private String prefix = "";
private final RedisConnectionFactory connectionFactory;
private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();
public RedisAuthorizationCodeServices(RedisConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
public void setExpiration(long expiration) {
this.expiration = expiration;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
private RedisConnection getConnection() {
return connectionFactory.getConnection();
}
/**
* value序列化
* @param object
* @return
*/
private byte[] serialize(Object object) {
return serializationStrategy.serialize(object);
}
/**
* key序列化
* @param string
* @return
*/
private byte[] serialize(String string) {
return serializationStrategy.serialize(string);
}
/**
* key序列化
* @param object
* @return
*/
private byte[] serializeKey(Object object) {
return serialize(prefix + object);
}
/**
* 反序列化
* @param bytes
* @return
*/
private OAuth2Authentication deserializeAuthentication(byte[] bytes) {
return serializationStrategy.deserialize(bytes, OAuth2Authentication.class);
}
/**
* 将随机生成的授权码存到redis中
*
* @param code
* @param authentication
* @return void
*/
@Override
protected void store(String code, OAuth2Authentication authentication) {
byte[] serializedKey = serializeKey(AUTHORIZATION_CODE + code);
byte[] serializedAuthentication = serialize(authentication);
RedisConnection conn = getConnection();
try {
conn.openPipeline();
conn.set(serializedKey, serializedAuthentication);
conn.expire(serializedKey,expiration);
conn.closePipeline();
} finally {
conn.close();
}
}
/**
* 取出授权码并删除授权码(权限码只能用一次,调试时可不删除,code就可多次使用)
*
* @param code
* @return org.springframework.security.oauth2.provider.OAuth2Authentication
*/
@Override
protected OAuth2Authentication remove(String code) {
byte[] serializedKey = serializeKey(AUTHORIZATION_CODE + code);
RedisConnection conn = getConnection();
byte[] bytes;
try {
bytes = conn.get(serializedKey);
if (bytes != null) {
conn.del(serializedKey);
}
} finally {
conn.close();
}
return deserializeAuthentication(bytes);
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/config/RedisConfig.java
================================================
package cn.poile.ucs.auth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redis配置
* @author: yaohw
* @create: 2019-09-25 16:49
**/
@Configuration
public class RedisConfig {
/**
* 对象模板自定义存储序列化
*
* @param redisConnectionFactory
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
/**
* 对hash类型的数据操作
*
* @param redisTemplate
* @return
*/
@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
/**
* 对redis字符串类型数据操作
*
* @param redisTemplate
* @return
*/
@Bean
public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForValue();
}
/**
* 对链表类型的数据操作
*
* @param redisTemplate
* @return
*/
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
/**
* 对无序集合类型的数据操作
*
* @param redisTemplate
* @return
*/
@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
/**
* 对有序集合类型的数据操作
*
* @param redisTemplate
* @return
*/
@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
/**
* 字符串模板
*
* @param redisConnectionFactory
* @return
*/
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer());
return template;
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/config/ResourceServerConfig.java
================================================
package cn.poile.ucs.auth.config;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
/**
* 资源服务配置
* @author: yaohw
* @create: 2019-10-08 10:04
**/
@Configuration
// 启用资源服务
@EnableResourceServer
// 启用方法级权限控制
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Log4j2
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "auth-server";
/**
* 配置资源接口安全,http.authorizeRequests()针对的所有url,但是由于登录页面url包含在其中,这里配置会进行token校验,校验不通过返回错误json,
* 而授权码模式获取code时需要重定向登录页面,重定向过程并不能携带token,所有不能用http.authorizeRequests(),
* 而是用requestMatchers().antMatchers(""),这里配置的是需要资源接口拦截的url数组
* @param http
* @return void
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http //配置需要保护的资源接口
.requestMatchers().antMatchers("/user","/test/need_token","/logout","/remove","/update","/test/need_admin","/test/scope")
.and().authorizeRequests().anyRequest().authenticated();
}
/**
* 这个是跟服务绑定的,注意要跟client配置一致,如果客户端没有,则不能访问
* @param resources
* @throws Exception
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID).stateless(true);
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/config/SecurityConfigurerAdapter.java
================================================
package cn.poile.ucs.auth.config;
import cn.poile.ucs.auth.provider.MobileCodeAuthenticationProvider;
import cn.poile.ucs.auth.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.logout.LogoutFilter;
/**
* security web安全配置,spring-cloud-starter-oauth2依赖于security
* 默认情况下SecurityConfigurerAdapter执行比ResourceServerConfig先
* @author: yaohw
* @create: 2019-09-25 16:49
*/
@Configuration
@EnableWebSecurity()
public class SecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private IgnoreLogoutFilter ignoreLogoutFilter;
/**
* 配置认证管理器
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 配置密码加密对象(解密时会用到PasswordEncoder的matches判断是否正确)
* 用户的password和客户端clientSecret用到,所以存的时候存该bean encode过的密码
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 这里是对认证管理器的添加配置
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(provider())
.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**","/static/**");
}
/**
* 安全请求配置,这里配置的是security的部分,这里配置全部通过,安全拦截在资源服务的配置文件中配置,
* 要不然访问未验证的接口将重定向到登录页面,前后端分离的情况下这样并不友好,无权访问接口返回相关错误信息即可
* @param http
* @return void
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().loginPage("/login")
.permitAll()
.and().authorizeRequests().anyRequest().permitAll()
.and().csrf().disable().cors()
.and().addFilterAt(ignoreLogoutFilter, LogoutFilter.class);
}
/**
* 自定义手机验证码认证提供者
*
* @return
*/
@Bean
public MobileCodeAuthenticationProvider provider() {
MobileCodeAuthenticationProvider provider = new MobileCodeAuthenticationProvider();
provider.setStringRedisTemplate(stringRedisTemplate);
provider.setHideUserNotFoundExceptions(false);
provider.setUserDetailsService(userDetailsService);
return provider;
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/constant/RedisConstant.java
================================================
package cn.poile.ucs.auth.constant;
/**
* redis常量
* @author: yaohw
* @create: 2019-09-30 16:12
**/
public class RedisConstant {
public final static String SMS_CODE_PREFIX = "sms:code:";
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/controller/AuthenticationController.java
================================================
package cn.poile.ucs.auth.controller;
import cn.poile.ucs.auth.service.ClientDetailsServiceImpl;
import cn.poile.ucs.auth.vo.UserDetailImpl;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.security.Principal;
import java.util.*;
/**
* @author: yaohw
* @create: 2019-09-25 16:49
**/
@Controller
@Log4j2
public class AuthenticationController {
@Autowired
private ConsumerTokenServices consumerTokenServices;
@Autowired
private ClientDetailsServiceImpl clientDetailsService;
@Autowired
private TokenStore tokenStore;
@Autowired
private UserDetailsService userDetailsService;
/**
* 更新用户信息时更新redis中的用户信息
* @param authentication
* @return java.lang.String
*/
@GetMapping("/update")
public @ResponseBody String updateCacheUserInfo(Authentication authentication) {
if (authentication instanceof OAuth2Authentication) {
OAuth2Authentication auth2Authentication = (OAuth2Authentication) authentication;
Authentication userAuthentication = auth2Authentication.getUserAuthentication();
OAuth2Authentication newOAuth2Authentication = null;
if (userAuthentication instanceof UsernamePasswordAuthenticationToken) {
UserDetailImpl userDetails = (UserDetailImpl)userDetailsService.loadUserByUsername("yaohw");
userDetails.setUsername("yaohw2");
userDetails.setTest("test333");
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
newOAuth2Authentication= new OAuth2Authentication(auth2Authentication.getOAuth2Request(),usernamePasswordAuthenticationToken);
}
OAuth2AccessToken accessToken = tokenStore.getAccessToken(auth2Authentication);
if (newOAuth2Authentication != null) {
tokenStore.storeAccessToken(accessToken,newOAuth2Authentication);
}
}
return "ok";
}
/**
* 根据用户名和客户端id移除token
* @return
*/
@GetMapping("/update2")
public @ResponseBody String updateUserInfo() {
Collection<OAuth2AccessToken> tokensByClientIdAndUserName = tokenStore.findTokensByClientIdAndUserName("yaohw", "yaohw");
if (tokensByClientIdAndUserName != null) {
tokensByClientIdAndUserName.forEach(t -> consumerTokenServices.revokeToken(t.getValue()));
}
return "ok";
}
@GetMapping("/user")
public @ResponseBody Object userInfo(Principal user,Authentication authentication) {
log.info("user:{}",user);
log.info("auth:{}", authentication);
return user;
}
/**
* 退出时将token清空(使用RedisStore时就是删除掉对应缓存
* 注: 这里的路径不能使用/logout,因为这个路径被LogoutFilter占用,配置文件配置了访问logout会转发到这里
* 所以/logout和remove都能登出
* @param authorization
* @return
*/
@DeleteMapping("/remove")
public @ResponseBody String logout(@RequestHeader(value = "Authorization") String authorization) {
String accessToken = authorization.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim();
consumerTokenServices.revokeToken(accessToken);
return "ok";
}
/**
* 不需要token访问测试
* @return
*/
@GetMapping("/test/no_need_token")
public @ResponseBody String test() {
return "no_need_token";
}
/**
* 需要token访问接口测试
* @return
*/
@GetMapping("/test/need_token")
public @ResponseBody String test2() {
return "need_token";
}
/**
* 需要需要管理员权限
* @return
*/
@PreAuthorize("hasAuthority('admin')")
@GetMapping("/test/need_admin")
public @ResponseBody String admin() {
return "need_admin";
}
/**
* 认证页面
* @return ModelAndView
*/
@GetMapping("/login")
public ModelAndView require() {
log.info("---认证页面---");
return new ModelAndView("ftl/login");
}
/**
* scope 控制测试,该方法只有配置有scope为sever2的客户端能访问,针对的是客户端
* @return
*/
@GetMapping("/test/scope")
@PreAuthorize("#oauth2.hasScope('sever2')")
public @ResponseBody String test3() {
return "scope-test";
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/entity/SysAuthority.java
================================================
package cn.poile.ucs.auth.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
/**
* @author: yaohw
* @create: 2019-10-12 16:36
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(of = "authority")
public class SysAuthority implements GrantedAuthority {
/**
* 权限
*/
private String authority;
/**
* 权限描述
*/
private String desc;
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/entity/SysUser.java
================================================
package cn.poile.ucs.auth.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 这里可以看作数据库实体
* @author: yaohw
* @create: 2019-10-12 16:15
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SysUser {
private String id;
private String username;
private String password;
private String test;
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/granter/MobileCodeTokenGranter.java
================================================
package cn.poile.ucs.auth.granter;
import cn.poile.ucs.auth.Token.MobileCodeAuthenticationToken;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 自定义grant_type模式-手机号短信验证模式
* @author: yaohw
* @create: 2019-09-29 18:29
**/
public class MobileCodeTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "mobile";
private final AuthenticationManager authenticationManager;
public MobileCodeTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
}
private MobileCodeTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
String mobile = parameters.get("mobile");
String code = parameters.get("code");
Authentication userAuth = new MobileCodeAuthenticationToken(mobile,code);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = authenticationManager.authenticate(userAuth);
}
catch (AccountStatusException ase) {
//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
throw new InvalidGrantException(ase.getMessage());
}
catch (BadCredentialsException e) {
// If the username/password are wrong the spec says we should send 400/invalid grant
throw new InvalidGrantException(e.getMessage());
}
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate mobile: " + mobile);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/provider/MobileCodeAuthenticationProvider.java
================================================
package cn.poile.ucs.auth.provider;
import cn.poile.ucs.auth.Token.MobileCodeAuthenticationToken;
import cn.poile.ucs.auth.constant.RedisConstant;
import cn.poile.ucs.auth.service.UserDetailsServiceImpl;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* 手机模式认证提供者,手机验证码模式认证工作通过该类完成
* @author: yaohw
* @create: 2019-09-29 20:00
**/
@Log4j2
public class MobileCodeAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {
private StringRedisTemplate stringRedisTemplate;
private UserDetailsServiceImpl userDetailsService;
private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
/**
* 是否隐藏用户未发现异常,默认为true,为true返回的异常信息为BadCredentialsException
*/
private boolean hideUserNotFoundExceptions = true;
@Override
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String mobile = (String) authentication.getPrincipal();
if (mobile == null) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Missing mobile"));
}
String code = (String) authentication.getCredentials();
if (code == null) {
log.error("缺失code参数");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Missing code"));
}
String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstant.SMS_CODE_PREFIX + mobile);
if (cacheCode == null || !cacheCode.equals(code)) {
log.error("短信验证码错误");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Invalid code"));
}
//清除redis中的短信验证码
//stringRedisTemplate.delete(RedisConstant.SMS_CODE_PREFIX + mobile);
UserDetails user;
try {
user = userDetailsService.loadUserByMobile(mobile);
} catch (UsernameNotFoundException var6) {
log.info("手机号:" + mobile + "未查到用户信息");
if (this.hideUserNotFoundExceptions) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
throw var6;
}
check(user);
MobileCodeAuthenticationToken authenticationToken = new MobileCodeAuthenticationToken(user, code, user.getAuthorities());
authenticationToken.setDetails(authenticationToken.getDetails());
return authenticationToken;
}
/**
* 指定该认证提供者验证Token对象
* @param aClass
* @return
*/
@Override
public boolean supports(Class<?> aClass) {
return MobileCodeAuthenticationToken.class.isAssignableFrom(aClass);
}
/**
* 账号禁用、锁定、超时校验
*
* @param user
*/
private void check(UserDetails user) {
if (!user.isAccountNonLocked()) {
throw new LockedException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
} else if (!user.isEnabled()) {
throw new DisabledException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
} else if (!user.isAccountNonExpired()) {
throw new AccountExpiredException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
}
}
public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
}
public void setUserDetailsService(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/service/ClientDetailsServiceImpl.java
================================================
package cn.poile.ucs.auth.service;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Service;
/**
* @author: yaohw
* @create: 2019-10-12 16:12
**/
@Service
@Log4j2
public class ClientDetailsServiceImpl implements ClientDetailsService {
@Autowired
private SysClientDetailService clientDetailService;
/**
* Load a client by the client id. This method must not return null.
*
* @param clientId The client id.
* @return The client details (never null).
* @throws ClientRegistrationException If the client account is locked, expired, disabled, or invalid for any other reason.
*/
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
log.info("客户端查询:" + clientId);
BaseClientDetails baseClientDetails = clientDetailService.selectById(clientId);
if (baseClientDetails == null) {
throw new NoSuchClientException("not found clientId:" + clientId);
}
return baseClientDetails;
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/service/SysClientDetailService.java
================================================
package cn.poile.ucs.auth.service;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Oauth客户端服务
* @author: yaohw
* @create: 2019-10-12 17:33
**/
@Service
public class SysClientDetailService {
/**
* 根据客户端id查询
* @param clientId
* @return org.springframework.security.oauth2.provider.client.BaseClientDetails
*/
public BaseClientDetails selectById(String clientId) {
BaseClientDetails clientDetails = new BaseClientDetails();
clientDetails.setAuthorities(new ArrayList<>());
clientDetails.setClientId("yaohw");
// 这个客户端秘钥和密码一样存BCryptPasswordEncoder加密后的接口,具体看定义的加密器
clientDetails.setClientSecret("$2a$10$CwIutywnbs9bifHaY3Ezu.gYkWi4Zano8gVPq08hXjal6.uj.Yzuy");
// 设置accessToken和refreshToken的时效,如果不设置则使tokenServices的配置的
clientDetails.setAccessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(2));
clientDetails.setRefreshTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(30));
// 资源id列表,需要注意的是这里配置的需要与ResourceServerConfig中配置的相匹配
List<String> resourceIds = new ArrayList<>();
resourceIds.add("auth-server");
resourceIds.add("resource-server");
clientDetails.setResourceIds(resourceIds);
List<String> scopes = new ArrayList<>(1);
scopes.add("sever");
clientDetails.setScope(scopes);
List<String> grantTypes = new ArrayList<>(5);
grantTypes.add("password");
grantTypes.add("refresh_token");
grantTypes.add("authorization_code");
grantTypes.add("implicit");
grantTypes.add("mobile");
clientDetails.setAuthorizedGrantTypes(grantTypes);
Set<String> sets = new HashSet<>(1);
sets.add("http://www.baidu.com");
clientDetails.setRegisteredRedirectUri(sets);
List<String> autoApproveScopes = new ArrayList<>(1);
autoApproveScopes.add("sever");
// 自动批准作用于,授权码模式时使用,登录验证后直接返回code,不再需要下一步点击授权
clientDetails.setAutoApproveScopes(autoApproveScopes);
return clientDetails;
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/service/SysUserService.java
================================================
package cn.poile.ucs.auth.service;
import cn.poile.ucs.auth.entity.SysUser;
import org.springframework.stereotype.Service;
/**
* @author: yaohw
* @create: 2019-10-12 16:21
**/
@Service
public class SysUserService {
/**
* 根据用户名查询用户
* @param username
* @return cn.poile.ucs.auth.entity.SysUser
*/
public SysUser selectByUsername(String username) {
return new SysUser("1","yaohw","$2a$10$CwIutywnbs9bifHaY3Ezu.gYkWi4Zano8gVPq08hXjal6.uj.Yzuy","测试字段,根据用户名查询");
}
/**
* 根据手机号查询用户
* @param mobile
* @return cn.poile.ucs.auth.entity.SysUser
*/
public SysUser selectByMobile(String mobile) {
return new SysUser("2","yaohw2","$2a$10$CwIutywnbs9bifHaY3Ezu.gYkWi4Zano8gVPq08hXjal6.uj.Yzuy","测试字段,根据手机号查询");
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/service/UserDetailsServiceImpl.java
================================================
package cn.poile.ucs.auth.service;
import cn.poile.ucs.auth.entity.SysAuthority;
import cn.poile.ucs.auth.entity.SysUser;
import cn.poile.ucs.auth.vo.UserDetailImpl;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
/**
* @author yaohw
* @date 2019-09-25 15:25
*/
@Service
@Log4j2
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserService userService;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
log.info("密码模式查询用户信息");
SysUser sysUser = userService.selectByUsername(s);
if (sysUser == null) {
throw new UsernameNotFoundException("not found user:" + s);
}
UserDetailImpl userDetail = new UserDetailImpl();
userDetail.setEnable(true);
BeanUtils.copyProperties(sysUser,userDetail);
//这里权限列表,这个为方便直接下(实际开发中查询用户时连表查询出权限)
Set<SysAuthority> authoritySet = new HashSet<>();
authoritySet.add(new SysAuthority("admin","管理员权限"));
userDetail.setAuthorities(authoritySet);
return userDetail;
}
/**
* 这里模拟根据手机号查询用户
* @param mobile
* @return
* @throws UsernameNotFoundException
*/
public UserDetails loadUserByMobile(String mobile) throws UsernameNotFoundException {
log.info("手机号模式查询用户信息");
SysUser sysUser = userService.selectByMobile(mobile);
if (sysUser == null) {
throw new UsernameNotFoundException("not found mobile user:" + mobile);
}
UserDetailImpl userDetail = new UserDetailImpl();
BeanUtils.copyProperties(sysUser,userDetail);
userDetail.setAuthorities(new ArrayList<>());
userDetail.setEnable(true);
return userDetail;
}
}
================================================
FILE: auth-server/src/main/java/cn/poile/ucs/auth/vo/UserDetailImpl.java
================================================
package cn.poile.ucs.auth.vo;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author yaohw
* @date 2019-09-25 16:12
*/
public class UserDetailImpl implements UserDetails {
private String id;
private String username;
private String password;
private String test;
private boolean isEnable;
private Collection<? extends GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.isEnable;
}
public void setEnable(boolean enable) {
isEnable = enable;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setTest(String test) {
this.test = test;
}
public String getTest() {
return test;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
}
}
================================================
FILE: auth-server/src/main/resources/application-dev.yml
================================================
spring:
application:
name: auth-server
redis:
host: 127.0.0.1
password:
port: 6379
timeout: 3000
lettuce:
pool:
max-idle: 8
max-active: 8
max-wait: -1ms
min-idle: 0
server:
port: 8001
#服务器发现注册配置
eureka:
client:
serviceUrl:
#配置服务中心(可配置多个,用逗号隔开)
defaultZone: http://admin:admin@localhost:9000/eureka/
##开启日志DEBUG级别,便于查看调试信息
logging.level.org.springframework.security: DEBUG
================================================
FILE: auth-server/src/main/resources/application.yml
================================================
spring:
profiles:
active:
- dev
================================================
FILE: auth-server/src/main/resources/static/css/signin.css
================================================
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #eee;
}
.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-margin-top {
margin-top: 50px;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin .checkbox {
font-weight: normal;
}
.form-signin .form-control {
position: relative;
height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
footer{
text-align: center;
position:absolute;
bottom:0;
width:100%;
height:100px;
}
================================================
FILE: auth-server/src/main/resources/templates/ftl/login.ftl
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<title>yaohw微服务统一认证</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/signin.css" rel="stylesheet">
</head>
<body>
<div class="container form-margin-top">
<!-- 这里请求方法为post,配置的url跟formLogin().loginPage("/login")的login一致,如果配formLogin().loginPage("/login").loginProcessingUrl("/form") 那么配置form -->
<form class="form-signin" action="/login" method="POST">
<h2 class="form-signin-heading" align="center">统一认证系统</h2>
<input type="text" name="username" class="form-control form-margin-top" placeholder="账号" required autofocus>
<input type="password" name="password" class="form-control" placeholder="密码" required>
<button class="btn btn-lg btn-primary btn-block" type="submit">登 录</button>
</form>
</div>
<footer>
<p>support by: yaohw</p>
<p>email: <a href="mailto:yaohw484@gmail.com">yaohw484@gmail.com</a>.</p>
</footer>
</body>
</html>
================================================
FILE: eureka-server/.gitignore
================================================
/target/
================================================
FILE: eureka-server/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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud-oauth2</artifactId>
<groupId>cn.poile.ucs</groupId>
<version>1.0</version>
</parent>
<packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion>
<name>eureka-server</name>
<artifactId>eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 这里配置的security跟本次的主题oauth2没有关联,这里加这个配置单纯的只是为了给注册中心添加安全配置,
要不然只要知道链接,谁都可以看到并用你的注册中心(非内网情况下)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
================================================
FILE: eureka-server/src/main/java/cn/poile/ucs/eureka/EurekaServerApplication.java
================================================
package cn.poile.ucs.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* 注册中心
* @author: yaohw
* @create: 2019-09-25 16:10
**/
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class,args);
}
}
================================================
FILE: eureka-server/src/main/java/cn/poile/ucs/eureka/config/WebSecurityConfig.java
================================================
package cn.poile.ucs.eureka.config;
import org.springframework.context.annotation.Configuration;
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;
/**
* Security 配置
* @author: yaohw
* @create: 2019-09-25 17:27
**/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 默认是开启,所以要关闭,否则其他服务无法注册到注册中心
http.csrf().disable();
super.configure(http);
}
}
================================================
FILE: eureka-server/src/main/resources/application-dev.yml
================================================
spring:
security:
user:
name: admin
password: admin
application:
name: eureka-server
server:
port: 9000
eureka:
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://admin:admin@localhost:9000/eureka/
================================================
FILE: eureka-server/src/main/resources/application.yml
================================================
spring:
profiles:
active:
- dev
================================================
FILE: 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.poile.ucs</groupId>
<artifactId>springcloud-oauth2</artifactId>
<packaging>pom</packaging>
<version>1.0</version>
<modules>
<module>eureka-server</module>
<module>auth-server</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-boot.version>2.1.8.RELEASE</spring-boot.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
<!--Lombok-->
<lombok.version>1.18.10</lombok.version>
<commons-io.version>2.6</commons-io.version>
<javadoc.version>3.0.0</javadoc.version>
</properties>
<dependencies>
<!--注册中心-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--配置文件处理器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>
</project>
================================================
FILE: resource-server/.gitignore
================================================
/target/
================================================
FILE: resource-server/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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud-oauth2</artifactId>
<groupId>cn.poile.ucs</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>resources-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--oauth2.0-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
================================================
FILE: resource-server/src/main/java/cn/poile/ucs/resources/ResourceServerApplication.java
================================================
package cn.poile.ucs.resources;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* 资源服务
* @author: yaohw
* @create: 2019-10-08 10:02
**/
@SpringBootApplication
@EnableDiscoveryClient
public class ResourceServerApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceServerApplication.class,args);
}
}
================================================
FILE: resource-server/src/main/java/cn/poile/ucs/resources/config/CustomizePrincipalExtractor.java
================================================
package cn.poile.ucs.resources.config;
import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor;
import java.util.Map;
/**
* 自定义principal提取器
* @author: yaohw
* @create: 2019-10-09 12:01
**/
public class CustomizePrincipalExtractor implements PrincipalExtractor {
/**
* Extract the principal that should be used for the token.
*
* @param map the source map
* @return the extracted principal or {@code null}
*/
@Override
public Object extractPrincipal(Map<String, Object> map) {
// 这直接返回map本身,该map包含的认证中心对的principal的所有字段(key为字段名,value为字段值形式)
// 这里也可以new一个user对象,将map对应字段值映射到user对象中返回user对象
return map;
}
}
================================================
FILE: resource-server/src/main/java/cn/poile/ucs/resources/config/ResourceServerConfig.java
================================================
package cn.poile.ucs.resources.config;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
/**
* @author: yaohw
* @create: 2019-10-08 10:04
**/
@Configuration
@EnableResourceServer
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Log4j2
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource-server";
@Autowired
private UserInfoTokenServices userInfoTokenServices;
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 配置不需要安全拦截url
.antMatchers("/test/no_need_token").permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
}
/**
* 这个是跟服务绑定的,注意要跟client配置一致,如果客户端没有,则不能访问
* @param resources
* @throws Exception
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID).stateless(true);
userInfoTokenServices.setPrincipalExtractor(principalExtractor());
// 配置了user-info-uri默认使用的就是userInfoTokenServices,这个这么配置只是为了设置principalExtractor
resources.tokenServices(userInfoTokenServices);
}
/**
* 自定义Principal提取器,返回的Principal是一个map
*
* @return
*/
@Bean
public PrincipalExtractor principalExtractor() {
return new CustomizePrincipalExtractor();
}
}
================================================
FILE: resource-server/src/main/java/cn/poile/ucs/resources/controller/TestRestController.java
================================================
package cn.poile.ucs.resources.controller;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: yaohw
* @create: 2019-10-08 11:37
**/
@RestController
@Log4j2
public class TestRestController {
/**
* 不需要token访问测试
* @return
*/
@GetMapping("/test/no_need_token")
public @ResponseBody String test() {
return "no_need_token";
}
/**
* 需要需要token访问接口测试
* @return
*/
@GetMapping("/test/need_token")
public @ResponseBody String test2(Authentication authentication) {
log.info("{}",authentication);
// 由于自定义的principal返回的是包含全部user字段的map
Object principal = authentication.getPrincipal();
return "need_token";
}
/**
* 需要需要管理员权限
* @return
*/
@PreAuthorize("hasAuthority('admin')")
@GetMapping("/test/need_admin")
public @ResponseBody String admin() {
return "need_admin";
}
}
================================================
FILE: resource-server/src/main/resources/application-dev.yml
================================================
spring:
application:
name: resource-server
server:
port: 8003
#服务器发现注册配置
eureka:
client:
serviceUrl:
#配置服务中心(可配置多个,用逗号隔开)
defaultZone: http://admin:admin@localhost:9000/eureka/
##安全配置##
security:
oauth2:
resource:
id: resource-server
## user-info-uri和token-info-uri二选择即可
##如果配置了user-info-uri,该资源服务器使用userInfoTokenServices远程调用认证中心接口,通过认证中心的OAuth2AuthenticationProcessingFilter完成验证工作,一般设置user-info-uri即可
user-info-uri: http://127.0.0.1:8001/user
prefer-token-info: false
## 该资源服务器使用RemoteTokenServices远程调用认证中心接口,注意一点就是如果使用token-info-uri那么就必须设置上clientId和clientSecret,通过CheckTokenEndpoint完成验证工作
#token-info-uri: http://127.0.0.1:8001/oauth/check_token
#client:
#client-secret: yaohw
#client-id: yaohw
================================================
FILE: resource-server/src/main/resources/application.yml
================================================
spring:
profiles:
active:
- dev
================================================
FILE: resource-server/target/classes/application-dev.yml
================================================
spring:
application:
name: resource-server
server:
port: 8003
#服务器发现注册配置
eureka:
client:
serviceUrl:
#配置服务中心(可配置多个,用逗号隔开)
defaultZone: http://admin:admin@localhost:9000/eureka/
##安全配置##
security:
oauth2:
resource:
id: resource-server
## user-info-uri和token-info-uri二选择即可
##如果配置了user-info-uri,该资源服务器使用userInfoTokenServices远程调用认证中心接口,通过认证中心的OAuth2AuthenticationProcessingFilter完成验证工作,一般设置user-info-uri即可
user-info-uri: http://127.0.0.1:8001/user
prefer-token-info: false
## 该资源服务器使用RemoteTokenServices远程调用认证中心接口,注意一点就是如果使用token-info-uri那么就必须设置上clientId和clientSecret,通过CheckTokenEndpoint完成验证工作
#token-info-uri: http://127.0.0.1:8001/oauth/check_token
#client:
#client-secret: yaohw
#client-id: yaohw
================================================
FILE: resource-server/target/classes/application.yml
================================================
spring:
profiles:
active:
- dev
================================================
FILE: source_note/OAuth2ClientAuthenticationProcessingFilter.java
================================================
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
OAuth2AccessToken accessToken;
try {
// 获取当前token,需要注意一点这个虽然用到restTemplate,但实际上这里并没有发起远程调度,这里restTemplate是OAuth2RestTemplate的实例
// 一路点进去你会发现他只是从上下文获取到accessToken
accessToken = restTemplate.getAccessToken();
} catch (OAuth2Exception e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
try {
// 这步是校验token的关键,这里tokenServices是ResourceServerTokenServices实例,这里做怎么样的操作取决是注入的ResourceServerTokenServices
// 默认情况下ResourceServerTokenServices的实例DefaultTokenServices
// 认证中心默认的就是DefaultTokenServices,这个类做的就是从OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue)
// 我们配置中心配置的tokenStore的是RedisTokenStore,所以实际上她做的就是从redis中读取出accessToken相关信息
<!-- 分割线 --->
// 上面说的DefaultTokenServices是认证中心token的处理,资源服务下:
// 如果配置文件中配置的user-info-uri则ResourceServerTokenServices注入的实例将是UserInfoTokenServices的实例
// 如果配置token-info-uri则ResourceServerTokenServices注入的实例将是RemoteTokenServices
// 如果两者都配置了,优先UserInfoTokenServices
// UserInfoTokenServices和RemoteTokenServices做的事都是远程调度认证中心相应的接口完成token的校验
// 两者主要区别在于RemoteTokenServices需要配置clientId和clientSecret
// RemoteTokenServices中有这么一句话:Null Client ID or Client Secret detected. Endpoint that requires authentication will reject request with 401 error.
// 具体请查看RemoteTokenServices和UserInfoTokenServices
OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());
if (authenticationDetailsSource!=null) {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
result.setDetails(authenticationDetailsSource.buildDetails(request));
}
publish(new AuthenticationSuccessEvent(result));
return result;
}
catch (InvalidTokenException e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
}
================================================
FILE: source_note/RoleVoter.java
================================================
/***
* 方法级-权限校验
*/
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
// 这里的Authentication是经过OAuth2ClientAuthenticationProcessingFilter过滤的Authentication
// 如果等于null 返回拒绝编码
if (authentication == null) {
return ACCESS_DENIED;
}
// 赋值弃用编码,也就是我们方法那里没加有对应的用户权限注解
int result = ACCESS_ABSTAIN;
// 取出token中的权限列表
Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);
//
for (ConfigAttribute attribute : attributes) {
if (this.supports(attribute)) {
result = ACCESS_DENIED;
// Attempt to find a matching granted authority
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}
================================================
FILE: source_note/TokenEndpoint_source_note.java
================================================
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException(
"There is no client authentication. Try adding an appropriate authentication filter.");
}
// 根据当前请求获取到clientId
String clientId = getClientId(principal);
//获取当前ClientDetailsService(就是我们在AuthorizationConfig中配置)然后根据clientId去数据库查询客户端详情
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
// 将请求参数封装成TokenRequest
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
// 请求的clientId与查出来的匹配
if (clientId != null && !clientId.equals("")) {
// Only validate the client details if a client authenticated during this
// request.
if (!clientId.equals(tokenRequest.getClientId())) {
// double check to make sure that the client ID in the token request is the same as that in the
// authenticated client
throw new InvalidClientException("Given client ID does not match authenticated client");
}
}
// 校验客户端范围
if (authenticatedClient != null) {
oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
}
if (!StringUtils.hasText(tokenRequest.getGrantType())) {
throw new InvalidRequestException("Missing grant type");
}
// 判断是否是简化模式(简化模式不是这个接口,走的是AuthorizationEndpoint类下的/oauth/authorize)
if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
}
// 判断是否简化模式,如果是,清空返回,因为简化模式在第一步获取code的时候就将client信息缓存起来的,后面检验的是从缓存取出来补充完整
if (isAuthCodeRequest(parameters)) {
// The scope was requested or determined during the authorization step
if (!tokenRequest.getScope().isEmpty()) {
logger.debug("Clearing scope of incoming token request");
tokenRequest.setScope(Collections.<String> emptySet());
}
}
// 是否刷新token模式
if (isRefreshTokenRequest(parameters)) {
// A refresh token has its own default scopes, so we should ignore any added by the factory here.
tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
}
// 这步是整个认证的关键,这里简单说下流程,首先她会根据当前请求的grantType找到对应的认证模式,比如密码模式的ResourceOwnerPasswordTokenGranter,
// 然后对应的AbstractTokenGranter调用对应的grant方法,grant方法中又调用经过一系列调用,在getOAuth2Authentication方法中生成对应的AbstractAuthenticationToken,比如UsernamePasswordAuthenticationToken,
// 然后认证管理器(就是我们在AuthorizationConfig中配置的AuthenticationManager)调用认证方法authenticationManager.authenticate(abstractAuthenticationToken)
// AbstractAuthenticationToken和AuthenticationProvider是存在一一对应的关系
// 比如UsernamePasswordAuthenticationToken和DaoAuthenticationProvider,authenticationManager.authenticate()会根据传入的AbstractAuthenticationToken找到对应的AuthenticationProvider,
// 真正认证逻辑通过AuthenticationProvider来完成的,比如密码模式的DaoAuthenticationProvider,会去根据用户名查询出对应的用户,然后校验用户密码是否匹配,用户是否锁定过期等
// 具体可查看DaoAuthenticationProvider和她继承的AbstractUserDetailsAuthenticationProvider
// 理清上面的思路后,我们就可以自定义grantType,就是定义一个继承AbstractTokenGranter的类,重写getOAuth2Authentication方法,该方法里面会用到AbstractAuthenticationToken和AuthenticationProvider
// 我们再分别一个类分别继承对应的类即可(大概思路,具体查看代码)
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
}
//这个没什么好说的,就是http请求响应体封装
return getResponse(token);
}
gitextract_uukuw93h/
├── .gitignore
├── LICENSE
├── README.md
├── auth-server/
│ ├── .gitignore
│ ├── pom.xml
│ └── src/
│ ├── .gitignore
│ └── main/
│ ├── java/
│ │ └── cn/
│ │ └── poile/
│ │ └── ucs/
│ │ └── auth/
│ │ ├── AuthServerApplication.java
│ │ ├── Token/
│ │ │ └── MobileCodeAuthenticationToken.java
│ │ ├── config/
│ │ │ ├── AuthorizationConfig.java
│ │ │ ├── IgnoreLogoutFilter.java
│ │ │ ├── RedisAuthorizationCodeServices.java
│ │ │ ├── RedisConfig.java
│ │ │ ├── ResourceServerConfig.java
│ │ │ └── SecurityConfigurerAdapter.java
│ │ ├── constant/
│ │ │ └── RedisConstant.java
│ │ ├── controller/
│ │ │ └── AuthenticationController.java
│ │ ├── entity/
│ │ │ ├── SysAuthority.java
│ │ │ └── SysUser.java
│ │ ├── granter/
│ │ │ └── MobileCodeTokenGranter.java
│ │ ├── provider/
│ │ │ └── MobileCodeAuthenticationProvider.java
│ │ ├── service/
│ │ │ ├── ClientDetailsServiceImpl.java
│ │ │ ├── SysClientDetailService.java
│ │ │ ├── SysUserService.java
│ │ │ └── UserDetailsServiceImpl.java
│ │ └── vo/
│ │ └── UserDetailImpl.java
│ └── resources/
│ ├── application-dev.yml
│ ├── application.yml
│ ├── static/
│ │ └── css/
│ │ └── signin.css
│ └── templates/
│ └── ftl/
│ └── login.ftl
├── eureka-server/
│ ├── .gitignore
│ ├── pom.xml
│ └── src/
│ └── main/
│ ├── java/
│ │ └── cn/
│ │ └── poile/
│ │ └── ucs/
│ │ └── eureka/
│ │ ├── EurekaServerApplication.java
│ │ └── config/
│ │ └── WebSecurityConfig.java
│ └── resources/
│ ├── application-dev.yml
│ └── application.yml
├── pom.xml
├── resource-server/
│ ├── .gitignore
│ ├── pom.xml
│ ├── src/
│ │ └── main/
│ │ ├── java/
│ │ │ └── cn/
│ │ │ └── poile/
│ │ │ └── ucs/
│ │ │ └── resources/
│ │ │ ├── ResourceServerApplication.java
│ │ │ ├── config/
│ │ │ │ ├── CustomizePrincipalExtractor.java
│ │ │ │ └── ResourceServerConfig.java
│ │ │ └── controller/
│ │ │ └── TestRestController.java
│ │ └── resources/
│ │ ├── application-dev.yml
│ │ └── application.yml
│ └── target/
│ └── classes/
│ ├── application-dev.yml
│ └── application.yml
└── source_note/
├── OAuth2ClientAuthenticationProcessingFilter.java
├── RoleVoter.java
└── TokenEndpoint_source_note.java
SYMBOL INDEX (116 symbols across 27 files)
FILE: auth-server/src/main/java/cn/poile/ucs/auth/AuthServerApplication.java
class AuthServerApplication (line 15) | @SpringBootApplication
method main (line 20) | public static void main(String[] args) {
FILE: auth-server/src/main/java/cn/poile/ucs/auth/Token/MobileCodeAuthenticationToken.java
class MobileCodeAuthenticationToken (line 13) | public class MobileCodeAuthenticationToken extends AbstractAuthenticatio...
method MobileCodeAuthenticationToken (line 18) | public MobileCodeAuthenticationToken(Object principal, Object credenti...
method MobileCodeAuthenticationToken (line 25) | public MobileCodeAuthenticationToken(Object principal, Object credenti...
method getCredentials (line 32) | @Override
method getPrincipal (line 37) | @Override
method setAuthenticated (line 42) | @Override
method eraseCredentials (line 51) | @Override
FILE: auth-server/src/main/java/cn/poile/ucs/auth/config/AuthorizationConfig.java
class AuthorizationConfig (line 1) | @Configuration
@EnableAuthorizationServer
public class AuthorizationConf...
method tokenStore (line 1) | @Bean
public TokenStore tokenStore() {
return new RedisTok...
method authorizationCodeServices (line 1) | @Primary
@Bean
public AuthorizationCodeServices authorizationC...
method configure (line 1) | @Override
public void configure(ClientDetailsServiceConfigurer cli...
method configure (line 1) | @Override
public void configure(AuthorizationServerEndpointsConfig...
FILE: auth-server/src/main/java/cn/poile/ucs/auth/config/IgnoreLogoutFilter.java
class IgnoreLogoutFilter (line 20) | @Component
method IgnoreLogoutFilter (line 26) | public IgnoreLogoutFilter() {
method doFilter (line 31) | @Override
method requiresLogout (line 44) | protected boolean requiresLogout(HttpServletRequest request, HttpServl...
method setFilterProcessesUrl (line 48) | public void setFilterProcessesUrl(String filterProcessesUrl) {
FILE: auth-server/src/main/java/cn/poile/ucs/auth/config/RedisAuthorizationCodeServices.java
class RedisAuthorizationCodeServices (line 20) | public class RedisAuthorizationCodeServices extends RandomValueAuthoriza...
method RedisAuthorizationCodeServices (line 40) | public RedisAuthorizationCodeServices(RedisConnectionFactory connectio...
method setExpiration (line 46) | public void setExpiration(long expiration) {
method setPrefix (line 51) | public void setPrefix(String prefix) {
method getConnection (line 55) | private RedisConnection getConnection() {
method serialize (line 64) | private byte[] serialize(Object object) {
method serialize (line 73) | private byte[] serialize(String string) {
method serializeKey (line 82) | private byte[] serializeKey(Object object) {
method deserializeAuthentication (line 92) | private OAuth2Authentication deserializeAuthentication(byte[] bytes) {
method store (line 107) | @Override
method remove (line 129) | @Override
FILE: auth-server/src/main/java/cn/poile/ucs/auth/config/RedisConfig.java
class RedisConfig (line 15) | @Configuration
method redisTemplate (line 25) | @Bean
method hashOperations (line 42) | @Bean
method valueOperations (line 53) | @Bean
method listOperations (line 64) | @Bean
method setOperations (line 75) | @Bean
method zSetOperations (line 86) | @Bean
method stringRedisTemplate (line 97) | @Bean
FILE: auth-server/src/main/java/cn/poile/ucs/auth/config/ResourceServerConfig.java
class ResourceServerConfig (line 16) | @Configuration
method configure (line 34) | @Override
method configure (line 46) | @Override
FILE: auth-server/src/main/java/cn/poile/ucs/auth/config/SecurityConfigurerAdapter.java
class SecurityConfigurerAdapter (line 1) | @Configuration
@EnableWebSecurity()
public class SecurityConfigurerAdapt...
method authenticationManagerBean (line 1) | @Bean
@Override
public AuthenticationManager authenticationMan...
method passwordEncoder (line 1) | @Bean
public PasswordEncoder passwordEncoder() {
return ne...
method configure (line 1) | @Override
protected void configure(AuthenticationManagerBuilder au...
method configure (line 1) | @Override
public void configure(WebSecurity web) throws Exception ...
method configure (line 1) | @Override
protected void configure(HttpSecurity http) throws Excep...
method provider (line 1) | @Bean
public MobileCodeAuthenticationProvider provider() {
...
FILE: auth-server/src/main/java/cn/poile/ucs/auth/constant/RedisConstant.java
class RedisConstant (line 8) | public class RedisConstant {
FILE: auth-server/src/main/java/cn/poile/ucs/auth/controller/AuthenticationController.java
class AuthenticationController (line 28) | @Controller
method updateCacheUserInfo (line 49) | @GetMapping("/update")
method updateUserInfo (line 74) | @GetMapping("/update2")
method userInfo (line 83) | @GetMapping("/user")
method logout (line 97) | @DeleteMapping("/remove")
method test (line 108) | @GetMapping("/test/no_need_token")
method test2 (line 117) | @GetMapping("/test/need_token")
method admin (line 126) | @PreAuthorize("hasAuthority('admin')")
method require (line 136) | @GetMapping("/login")
method test3 (line 146) | @GetMapping("/test/scope")
FILE: auth-server/src/main/java/cn/poile/ucs/auth/entity/SysAuthority.java
class SysAuthority (line 13) | @Data
FILE: auth-server/src/main/java/cn/poile/ucs/auth/entity/SysUser.java
class SysUser (line 12) | @Data
FILE: auth-server/src/main/java/cn/poile/ucs/auth/granter/MobileCodeTokenGranter.java
class MobileCodeTokenGranter (line 19) | public class MobileCodeTokenGranter extends AbstractTokenGranter {
method MobileCodeTokenGranter (line 25) | public MobileCodeTokenGranter(AuthenticationManager authenticationMana...
method MobileCodeTokenGranter (line 30) | private MobileCodeTokenGranter(AuthenticationManager authenticationMan...
method getOAuth2Authentication (line 36) | @Override
FILE: auth-server/src/main/java/cn/poile/ucs/auth/provider/MobileCodeAuthenticationProvider.java
class MobileCodeAuthenticationProvider (line 23) | @Log4j2
method setMessageSource (line 37) | @Override
method authenticate (line 42) | @Override
method supports (line 82) | @Override
method check (line 92) | private void check(UserDetails user) {
method setStringRedisTemplate (line 102) | public void setStringRedisTemplate(StringRedisTemplate stringRedisTemp...
method setHideUserNotFoundExceptions (line 106) | public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExce...
method setUserDetailsService (line 110) | public void setUserDetailsService(UserDetailsServiceImpl userDetailsSe...
FILE: auth-server/src/main/java/cn/poile/ucs/auth/service/ClientDetailsServiceImpl.java
class ClientDetailsServiceImpl (line 16) | @Service
method loadClientByClientId (line 30) | @Override
FILE: auth-server/src/main/java/cn/poile/ucs/auth/service/SysClientDetailService.java
class SysClientDetailService (line 17) | @Service
method selectById (line 25) | public BaseClientDetails selectById(String clientId) {
FILE: auth-server/src/main/java/cn/poile/ucs/auth/service/SysUserService.java
class SysUserService (line 10) | @Service
method selectByUsername (line 18) | public SysUser selectByUsername(String username) {
method selectByMobile (line 27) | public SysUser selectByMobile(String mobile) {
FILE: auth-server/src/main/java/cn/poile/ucs/auth/service/UserDetailsServiceImpl.java
class UserDetailsServiceImpl (line 1) | @Service
@Log4j2
public class UserDetailsServiceImpl implements UserDeta...
method loadUserByUsername (line 1) | @Override
public UserDetails loadUserByUsername(String s) throws U...
FILE: auth-server/src/main/java/cn/poile/ucs/auth/vo/UserDetailImpl.java
class UserDetailImpl (line 1) | public class UserDetailImpl implements UserDetails {
private String...
method getAuthorities (line 1) | @Override
public Collection<? extends GrantedAuthority> getAuthori...
method getPassword (line 1) | @Override
public String getPassword() {
return this.passwo...
method getUsername (line 1) | @Override
public String getUsername() {
return this.userna...
method isAccountNonExpired (line 1) | @Override
public boolean isAccountNonExpired() {
return tr...
method isAccountNonLocked (line 1) | @Override
public boolean isAccountNonLocked() {
return tru...
method isCredentialsNonExpired (line 1) | @Override
public boolean isCredentialsNonExpired() {
retur...
method isEnabled (line 1) | @Override
public boolean isEnabled() {
return this.isEnabl...
method setEnable (line 1) | public void setEnable(boolean enable) {
isEnable = enable;
}
method setUsername (line 1) | public void setUsername(String username) {
this.username = use...
method setPassword (line 1) | public void setPassword(String password) {
this.password = pas...
method setTest (line 1) | public void setTest(String test) {
this.test = test;
}
method getTest (line 1) | public String getTest() {
return test;
}
method getId (line 1) | public String getId() {
return id;
}
method setId (line 1) | public void setId(String id) {
this.id = id;
}
method setAuthorities (line 1) | public void setAuthorities(Collection<? extends GrantedAuthority> auth...
FILE: eureka-server/src/main/java/cn/poile/ucs/eureka/EurekaServerApplication.java
class EurekaServerApplication (line 12) | @EnableEurekaServer
method main (line 16) | public static void main(String[] args) {
FILE: eureka-server/src/main/java/cn/poile/ucs/eureka/config/WebSecurityConfig.java
class WebSecurityConfig (line 13) | @Configuration
method configure (line 16) | @Override
FILE: resource-server/src/main/java/cn/poile/ucs/resources/ResourceServerApplication.java
class ResourceServerApplication (line 12) | @SpringBootApplication
method main (line 16) | public static void main(String[] args) {
FILE: resource-server/src/main/java/cn/poile/ucs/resources/config/CustomizePrincipalExtractor.java
class CustomizePrincipalExtractor (line 12) | public class CustomizePrincipalExtractor implements PrincipalExtractor {
method extractPrincipal (line 20) | @Override
FILE: resource-server/src/main/java/cn/poile/ucs/resources/config/ResourceServerConfig.java
class ResourceServerConfig (line 21) | @Configuration
method configure (line 34) | @Override
method configure (line 49) | @Override
method principalExtractor (line 62) | @Bean
FILE: resource-server/src/main/java/cn/poile/ucs/resources/controller/TestRestController.java
class TestRestController (line 14) | @RestController
method test (line 23) | @GetMapping("/test/no_need_token")
method test2 (line 32) | @GetMapping("/test/need_token")
method admin (line 44) | @PreAuthorize("hasAuthority('admin')")
FILE: source_note/RoleVoter.java
method vote (line 4) | public int vote(Authentication authentication, Object object,
FILE: source_note/TokenEndpoint_source_note.java
method postAccessToken (line 2) | @RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
Condensed preview — 49 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (111K chars).
[
{
"path": ".gitignore",
"chars": 379,
"preview": "# Created by .ignore support plugin (hsz.mobi)\n### Java template\n# Compiled class file\n*.class\n\n# Log file\n*.log\n\n# Blue"
},
{
"path": "LICENSE",
"chars": 1070,
"preview": "MIT License\n\nCopyright (c) 2019-present Yaohw\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "README.md",
"chars": 18354,
"preview": "# 简介\n\n本项目基于spring-cloud-starter-oauth2搭建的认证中心和资源服务器的微服务项目,项目不仅仅简单的demo,项目的出发点在于实战应用。本项目为笔者花了不少时间和精力整理出来的,只需要稍微调整就可应用于实际项"
},
{
"path": "auth-server/.gitignore",
"chars": 9,
"preview": "/target/\n"
},
{
"path": "auth-server/pom.xml",
"chars": 1719,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n xmlns:xsi=\"http://www"
},
{
"path": "auth-server/src/.gitignore",
"chars": 7,
"preview": "/test/\n"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/AuthServerApplication.java",
"chars": 584,
"preview": "package cn.poile.ucs.auth;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfig"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/Token/MobileCodeAuthenticationToken.java",
"chars": 1567,
"preview": "package cn.poile.ucs.auth.Token;\n\nimport org.springframework.security.authentication.AbstractAuthenticationToken;\nimport"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/config/AuthorizationConfig.java",
"chars": 7945,
"preview": "package cn.poile.ucs.auth.config;\r\rimport cn.poile.ucs.auth.granter.MobileCodeTokenGranter;\rimport cn.poile.ucs.auth.ser"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/config/IgnoreLogoutFilter.java",
"chars": 1716,
"preview": "package cn.poile.ucs.auth.config;\n\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.O"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/config/RedisAuthorizationCodeServices.java",
"chars": 3724,
"preview": "package cn.poile.ucs.auth.config;\n/**\n * 注意一点,这里存的redis的序列表用默认的JdkSerializationStrategy,跟RedisTokenStore类似\n * 不能用json的,使"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/config/RedisConfig.java",
"chars": 2792,
"preview": "package cn.poile.ucs.auth.config;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.contex"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/config/ResourceServerConfig.java",
"chars": 1870,
"preview": "package cn.poile.ucs.auth.config;\n\nimport lombok.extern.log4j.Log4j2;\nimport org.springframework.context.annotation.Conf"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/config/SecurityConfigurerAdapter.java",
"chars": 3634,
"preview": "package cn.poile.ucs.auth.config;\r\rimport cn.poile.ucs.auth.provider.MobileCodeAuthenticationProvider;\rimport cn.poile.u"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/constant/RedisConstant.java",
"chars": 199,
"preview": "package cn.poile.ucs.auth.constant;\n\n/**\n * redis常量\n * @author: yaohw\n * @create: 2019-09-30 16:12\n **/\npublic class Red"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/controller/AuthenticationController.java",
"chars": 5200,
"preview": "package cn.poile.ucs.auth.controller;\n\nimport cn.poile.ucs.auth.service.ClientDetailsServiceImpl;\nimport cn.poile.ucs.au"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/entity/SysAuthority.java",
"chars": 522,
"preview": "package cn.poile.ucs.auth.entity;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/entity/SysUser.java",
"chars": 374,
"preview": "package cn.poile.ucs.auth.entity;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/granter/MobileCodeTokenGranter.java",
"chars": 2961,
"preview": "package cn.poile.ucs.auth.granter;\n\nimport cn.poile.ucs.auth.Token.MobileCodeAuthenticationToken;\nimport org.springframe"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/provider/MobileCodeAuthenticationProvider.java",
"chars": 4768,
"preview": "package cn.poile.ucs.auth.provider;\n\nimport cn.poile.ucs.auth.Token.MobileCodeAuthenticationToken;\nimport cn.poile.ucs.a"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/service/ClientDetailsServiceImpl.java",
"chars": 1498,
"preview": "package cn.poile.ucs.auth.service;\n\nimport lombok.extern.log4j.Log4j2;\nimport org.springframework.beans.factory.annotati"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/service/SysClientDetailService.java",
"chars": 2265,
"preview": "package cn.poile.ucs.auth.service;\n\nimport org.springframework.security.oauth2.provider.client.BaseClientDetails;\nimport"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/service/SysUserService.java",
"chars": 788,
"preview": "package cn.poile.ucs.auth.service;\n\nimport cn.poile.ucs.auth.entity.SysUser;\nimport org.springframework.stereotype.Servi"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/service/UserDetailsServiceImpl.java",
"chars": 2208,
"preview": "package cn.poile.ucs.auth.service;\r\rimport cn.poile.ucs.auth.entity.SysAuthority;\rimport cn.poile.ucs.auth.entity.SysUse"
},
{
"path": "auth-server/src/main/java/cn/poile/ucs/auth/vo/UserDetailImpl.java",
"chars": 1820,
"preview": "package cn.poile.ucs.auth.vo;\r\rimport org.springframework.security.core.GrantedAuthority;\rimport org.springframework.sec"
},
{
"path": "auth-server/src/main/resources/application-dev.yml",
"chars": 462,
"preview": "spring:\n application:\n name: auth-server\n redis:\n host: 127.0.0.1\n password:\n port: 6379\n timeout: 3000"
},
{
"path": "auth-server/src/main/resources/application.yml",
"chars": 41,
"preview": "spring:\n profiles:\n active:\n - dev"
},
{
"path": "auth-server/src/main/resources/static/css/signin.css",
"chars": 1742,
"preview": "/*\n * Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms,"
},
{
"path": "auth-server/src/main/resources/templates/ftl/login.ftl",
"chars": 1355,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE="
},
{
"path": "eureka-server/.gitignore",
"chars": 9,
"preview": "/target/\n"
},
{
"path": "eureka-server/pom.xml",
"chars": 1298,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n xmlns:xsi=\"http://www"
},
{
"path": "eureka-server/src/main/java/cn/poile/ucs/eureka/EurekaServerApplication.java",
"chars": 493,
"preview": "package cn.poile.ucs.eureka;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autocon"
},
{
"path": "eureka-server/src/main/java/cn/poile/ucs/eureka/config/WebSecurityConfig.java",
"chars": 738,
"preview": "package cn.poile.ucs.eureka.config;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframe"
},
{
"path": "eureka-server/src/main/resources/application-dev.yml",
"chars": 280,
"preview": "spring:\n security:\n user:\n name: admin\n password: admin\n application:\n name: eureka-server\nserver:\n p"
},
{
"path": "eureka-server/src/main/resources/application.yml",
"chars": 43,
"preview": "spring:\n profiles:\n active:\n - dev"
},
{
"path": "pom.xml",
"chars": 2748,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n xmlns:xsi=\"http://www"
},
{
"path": "resource-server/.gitignore",
"chars": 9,
"preview": "/target/\n"
},
{
"path": "resource-server/pom.xml",
"chars": 1132,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n xmlns:xsi=\"http://www"
},
{
"path": "resource-server/src/main/java/cn/poile/ucs/resources/ResourceServerApplication.java",
"chars": 501,
"preview": "package cn.poile.ucs.resources;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.auto"
},
{
"path": "resource-server/src/main/java/cn/poile/ucs/resources/config/CustomizePrincipalExtractor.java",
"chars": 710,
"preview": "package cn.poile.ucs.resources.config;\n\nimport org.springframework.boot.autoconfigure.security.oauth2.resource.Principal"
},
{
"path": "resource-server/src/main/java/cn/poile/ucs/resources/config/ResourceServerConfig.java",
"chars": 2491,
"preview": "package cn.poile.ucs.resources.config;\n\nimport lombok.extern.log4j.Log4j2;\nimport org.springframework.beans.factory.anno"
},
{
"path": "resource-server/src/main/java/cn/poile/ucs/resources/controller/TestRestController.java",
"chars": 1217,
"preview": "package cn.poile.ucs.resources.controller;\n\nimport lombok.extern.log4j.Log4j2;\nimport org.springframework.security.acces"
},
{
"path": "resource-server/src/main/resources/application-dev.yml",
"chars": 791,
"preview": "spring:\n application:\n name: resource-server\n\nserver:\n port: 8003\n\n#服务器发现注册配置\neureka:\n client:\n serviceUrl:\n "
},
{
"path": "resource-server/src/main/resources/application.yml",
"chars": 41,
"preview": "spring:\n profiles:\n active:\n - dev"
},
{
"path": "resource-server/target/classes/application-dev.yml",
"chars": 791,
"preview": "spring:\n application:\n name: resource-server\n\nserver:\n port: 8003\n\n#服务器发现注册配置\neureka:\n client:\n serviceUrl:\n "
},
{
"path": "resource-server/target/classes/application.yml",
"chars": 41,
"preview": "spring:\n profiles:\n active:\n - dev"
},
{
"path": "source_note/OAuth2ClientAuthenticationProcessingFilter.java",
"chars": 2524,
"preview": "\r\r@Override\rpublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)\r "
},
{
"path": "source_note/RoleVoter.java",
"chars": 835,
"preview": "/***\n * 方法级-权限校验\n */\npublic int vote(Authentication authentication, Object object,\n\t\t\tCollection<ConfigAttribute> attrib"
},
{
"path": "source_note/TokenEndpoint_source_note.java",
"chars": 3668,
"preview": "\t\n\t@RequestMapping(value = \"/oauth/token\", method=RequestMethod.POST)\n\tpublic ResponseEntity<OAuth2AccessToken> postAcce"
}
]
About this extraction
This page contains the full source code of the copoile/springcloud-oauth2 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 49 files (89.8 KB), approximately 24.4k tokens, and a symbol index with 116 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.