項目地址百度網盤:https://pan.baidu.com/s/1B2UrLmFpQZPslgt9r-Uqmw
父工程使用的是spring-boot 2.1.3RELEASE
父工程依賴如下:
<?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>com.funtl</groupId>
<artifactId>oauth</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>spring-security-oauth2-server</module>
<module>spring-security-oauth2-server-JDBC</module>
<module>spring-security-oauth2-server-RBAC</module>
<module>spring-security-oauth2-resource</module>
</modules>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/>
</parent>
<properties>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
<mysql.version>5.1.32</mysql.version>
<mapper.starter.version>2.0.3</mapper.starter.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud -->
<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>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
子工程認證中心
pom依賴
<?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>oauth</artifactId>
<groupId>com.funtl</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.funtl</groupId>
<artifactId>spring-security-oauth2-server-RBAC</artifactId>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikaricp.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<!-- 排除 tomcat-jdbc 以使用 HikariCP -->
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.starter.version}</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-core</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-extra</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-base</artifactId>
<version>1.0.4</version>
</dependency>
</dependencies>
</project>
application.yml配置文件如下:
spring:
application:
name: oauth2-server
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.2.163:3306/oauth2?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
hikari:
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
pool-name: MyHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
redis:
host: 192.168.2.163
server:
port: 8080
mybatis:
type-aliases-package: oauth2.server.domain
mapper-locations: classpath:mapper/*.xml
啓動類:
package oauth2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan(basePackages = "oauth2.server.mapper")
public class RBACServerApplication {
public static void main(String[] args) {
SpringApplication.run(RBACServerApplication.class,args);
}
}
權限服務控制類:AuthorizationServerConfiguration
endpoints.tokenStore(new MyRedisTokenStoreService(redisConnectionFactory,null))設置的MyRedisTokenStoreService用刷新redis時限來實現用戶不掉線;endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))需要前端請求RefreshToken來獲取新的token實現不掉線
package oauth2.server.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
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.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import javax.sql.DataSource;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* 注入 AuthenticationManager 支持password grant_type
*/
@Autowired
private AuthenticationManager authenticationManagerBean;
/**
*Primary註解,去除系統自動配置的數據源
* @return
*/
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource(){
// 配置數據源(注意,我使用的是 HikariCP 連接池),以上註解是指定數據源,否則會有衝突
return DataSourceBuilder.create().build();
}
@Bean
public TokenStore tokenStore(){
// 基於 JDBC 實現,令牌保存到數據
return new JdbcTokenStore(dataSource());
}
@Bean
public ClientDetailsService jdbcDetailsService(){
// 基於 JDBC 實現,需要事先在數據庫配置客戶端信息
return new JdbcClientDetailsService(dataSource());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients()
.passwordEncoder(passwordEncoder)
.tokenKeyAccess("permitAll()") //允許/oauth/token調用
.checkTokenAccess("isAuthenticated()"); //允許/oauth/check_token被調用
}
/**
* 認證授權
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 讀取客戶端配置
//告訴springoauth2使用上面定義的jdbcDetailsService存儲
clients.withClientDetails(jdbcDetailsService())
;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 設置令牌
//告訴springoauth2使用上面定義的tokenStore存儲到數據庫
// endpoints.tokenStore(tokenStore())
//把token相關東西存入redis中,需要前端RefreshTokens來實現用戶不掉線
// endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
//自定義RedisTokenStore,可以根據用戶瀏覽刷新token時限
endpoints.tokenStore(new MyRedisTokenStoreService(redisConnectionFactory,null))
.authenticationManager(authenticationManagerBean);
//該字段設置設置refresh token是否重複使用,true:reuse;false:no reuse.
// .reuseRefreshTokens(false);
//配置TokenService參數
DefaultTokenServices tokenService = new DefaultTokenServices();
tokenService.setTokenStore(endpoints.getTokenStore());
tokenService.setSupportRefreshToken(true);
tokenService.setClientDetailsService(endpoints.getClientDetailsService());
tokenService.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenService.setAccessTokenValiditySeconds((int) TimeUnit.MINUTES.toSeconds(2)); //3分鐘
//refresh token時長大於2被的access_token時長,當access_token過期,
// 前端發送refresh_token重新獲取access_token和refresh_token,如此實現登錄一直操作登錄不過期
//https://www.code996.cn/post/2018/token-front/
tokenService.setRefreshTokenValiditySeconds((int)TimeUnit.MINUTES.toSeconds(6)); //6分鐘
//該字段設置設置refresh token是否重複使用,true:reuse;false:no reuse.
tokenService.setReuseRefreshToken(false);
endpoints.tokenServices(tokenService);
}
}
WebSecurityConfiguration類:
設置加密方式及制定自己實現的UserDetailsServiceImpl類來拓展權限
package oauth2.server.config;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.and()
.formLogin()
.loginPage("/login").failureForwardUrl("/login-error")
// .successForwardUrl("/index")
.permitAll();
}
@Bean
@Override
public UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl();
}
@Bean
public BCryptPasswordEncoder passwordEncoder(){
// 設置默認的加密方式
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
/**
* 管理器
* 加載這個管理器,讓其支持password模式授權
* @return
* @throws Exception
*/
@Bean
@Override
@SneakyThrows
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/oauth/check_token");
}
}
redis存儲token實現類MyRedisTokenStoreService
package oauth2.server.config;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import java.util.Date;
/**
* 2種方式續期token,1:前端接收到後臺返回401然後請求refresh_token重新獲取token
* 2:刷新redis裏的token時限
* access_token續期
* 自定義TokenStoreService
*/
public class MyRedisTokenStoreService extends RedisTokenStore {
private ClientDetailsService clientDetailsService;
public MyRedisTokenStoreService(RedisConnectionFactory connectionFactory, ClientDetailsService clientDetailsService) {
super(connectionFactory);
this.clientDetailsService = clientDetailsService;
}
@Override
public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
OAuth2Authentication result = readAuthentication(token.getValue());
if (result != null) {
// 如果token沒有失效 更新AccessToken過期時間
DefaultOAuth2AccessToken oAuth2AccessToken = (DefaultOAuth2AccessToken) token;
//重新設置過期時間
int validitySeconds = getAccessTokenValiditySeconds(result.getOAuth2Request());
if (validitySeconds > 0) {
oAuth2AccessToken.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
}
//將重新設置過的過期時間重新存入redis, 此時會覆蓋redis中原本的過期時間
storeAccessToken(token, result);
}
return result;
}
protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) {
if (clientDetailsService != null) {
ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
Integer validity = client.getAccessTokenValiditySeconds();
if (validity != null) {
return validity;
}
}
// 2 分鐘.
int accessTokenValiditySeconds = 60 * 2;
return accessTokenValiditySeconds;
}
}
UserDetailsServiceImpls實現類:通過查詢用戶權限並在ResourceServerConfiguration設置需要訪問的權限
package oauth2.server.config;
import oauth2.server.domain.TbPermission;
import oauth2.server.domain.TbUser;
import oauth2.server.service.TbPermissionService;
import oauth2.server.service.TbUserService;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private TbUserService tbUserService;
@Autowired
private TbPermissionService tbPermissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
TbUser tbUser = tbUserService.getByUsername(username);
List<GrantedAuthority> grantedAuthorities = Lists.newArrayList();
if (tbUser != null) {
// 獲取用戶授權
List<TbPermission> tbPermissions = tbPermissionService.selectByUserId(tbUser.getId());
// 聲明用戶授權
for (TbPermission tbPermission : tbPermissions) {
if (tbPermission != null && tbPermission.getEnname() != null) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(tbPermission.getEnname());
grantedAuthorities.add(grantedAuthority);
}
}
}
return new User(tbUser.getUsername(),tbUser.getPassword(),grantedAuthorities);
}
}
資源模塊ResourceServerConfiguration(在spring-security-oauth2-resource子模塊裏)類:
package com.oauth2.resource.config;
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.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/").hasAuthority("SystemContent")
.antMatchers("/view/**").hasAuthority("SystemContentView")
.antMatchers("/insert/**").hasAuthority("SystemContentInsert")
.antMatchers("/update/**").hasAuthority("SystemContentUpdate")
.antMatchers("/delete/**").hasAuthority("SystemContentDelete");
}
}
postman測試:
Authorization設置
頭設置
請求參數設置及獲取的token
使用access_token獲取資源