認證服務器:
package com.oath.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private TokenStore tokenStore;
@Autowired
private AccessTokenConverter accessTokenConverter;
@Autowired
private ClientDetailsService clientDetails;
// 配置令牌端點(Token Endpoint)的安全約束
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
// code授權添加
.realm("oauth2-resources")
// 開啓/oauth/token_key驗證端口無權限訪問
.tokenKeyAccess("permitAll()")
// 接口/oauth/check_token允許檢查令牌
.checkTokenAccess("isAuthenticated()")
// 使/oauth/token支持client_id以及client_secret作登錄認證
.allowFormAuthenticationForClients()
// 密碼編碼器
.passwordEncoder(passwordEncoder);
}
// 配置授權(authorization)以及令牌(token)的訪問端點和令牌服務(token services)
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
// 認證管理器
.authenticationManager(authenticationManager)
// 允許 GET、POST 請求獲取 token,即訪問端點:oauth/token
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
// 要使用refresh_token的話,需要額外配置userDetailsService
.userDetailsService(userDetailsService)
// 指定token存儲位置
.tokenStore(tokenStore)
// 配置JwtAccessToken轉換器
.accessTokenConverter(accessTokenConverter)
// 客戶端詳細信息服務的基本實現 這裏使用JdbcClientDetailsService
.setClientDetailsService(clientDetails);
}
// 配置客戶端詳情服務
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 內存模式
/**
* clients.inMemory().withClient("demoApp").secret(bCryptPasswordEncoder.encode("demoAppSecret"))
.redirectUris("http://baidu.com")// code授權添加
.authorizedGrantTypes("authorization_code", "client_credentials", "password", "refresh_token")
// scopes的值就是all(全部權限),read,write等權限。就是第三方訪問資源的一個權限,訪問範圍
.scopes("all")
// 這個資源服務的ID,這個屬性是可選的,但是推薦設置並在授權服務中進行驗證。
.resourceIds("oauth2-resource")
// 設置accessTokenValiditySeconds屬性來設置Access Token的存活時間。
.accessTokenValiditySeconds(1200)
// 設置refreshTokenValiditySeconds屬性來設置refresh Token的存活時間。
.refreshTokenValiditySeconds(50000);
*/
// 數據庫模式
clients.withClientDetails(clientDetails); // 表中存儲的secret值是加密後的值,並非明文;
}
}
資源服務器:
package com.oath.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/oauth/confirm_access").permitAll()
.antMatchers("/**/*.js").permitAll()
.antMatchers("/favicon.ico").permitAll()
.and()
.requestMatchers().antMatchers("/api/**").and().authorizeRequests().antMatchers("/api/**").authenticated();
}
}
package com.oath.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
@Configuration
public class AuthenticationBeanConfig {
@Autowired
private DataSource dataSource;
@Bean
@ConditionalOnMissingBean(PasswordEncoder.class)
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@ConditionalOnMissingBean(ClientDetailsService.class)
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
}
package com.oath.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component("userDetailsService")
public class DefaultUserDetailsService implements UserDetailsService {
private static final Logger log = LoggerFactory.getLogger(DefaultUserDetailsService.class);
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.debug("當前登陸用戶名爲:{}", username);
String password = passwordEncoder.encode("123456");
log.debug("當前登陸用戶名密碼爲:{}", password);
/**
* isEnabled 賬戶是否啓用
* isAccountNonExpired 賬戶沒有過期
* isCredentialsNonExpired 身份認證是否是有效的
* isAccountNonLocked 賬戶沒有被鎖定
* 對於 isAccountNonLocked 和 isEnabled 沒有做業務處理,只是拋出了對於的異常信息;
*/
// 賦予一個admin權限
// User admin = new User(username, password, true, true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_USER"));
}
}
安全配置:
package com.oath.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
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.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;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
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;
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private RedisConnectionFactory connectionFactory;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/oauth/**", "/login/**", "/logout/**")
.and()
.authorizeRequests()
.and()
.formLogin()
.permitAll()
.and()
.cors().disable();
}
@Bean
public TokenStore tokenStore() {
// 使用redis存儲token信息
RedisTokenStore redisTokenStore = new RedisTokenStore(connectionFactory);
return redisTokenStore;
// 使用jwt內存存儲token信息
// JwtTokenStore jwtTokenStore = new JwtTokenStore(accessTokenConverter());
// return jwtTokenStore;
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("healthy");
return converter;
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
/**
* *需要配置這個支持password模式 support password grant type
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
public static void main(String[] args) {
String encode = new BCryptPasswordEncoder().encode("demoAppSecret");
System.out.println(encode);
}
}
測試接口:
package com.oath.controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class HelloOath2Controller {
@RequestMapping("/hello/{id}")
public String helloOath2(@PathVariable long id) {
System.out.println("請求的ID編碼爲:" + id);
return "helloOath2";
}
}
啓動類:
package com.oath;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SecurityOauth2DemoApplication {
/**
* *【密碼授權模式-client】
* 密碼模式需要參數:username,password,grant_type,client_id,client_secret
* http://localhost:8080/oauth/token?username=demoUser1&password=123456&grant_type=password&client_id=demoApp&client_secret=demoAppSecret
*
* *【客戶端授權模式-password】 客戶端模式需要參數:grant_type,client_id,client_secret
* http://localhost:8080/oauth/token?grant_type=client_credentials&client_id=demoApp&client_secret=demoAppSecret
*
* *【授權碼模式-code】 獲取code
* http://localhost:8080/oauth/authorize?response_type=code&client_id=demoApp&redirect_uri=http://baidu.com
*
* *【通過code】 換token
* http://localhost:8080/oauth/token?grant_type=authorization_code&code=Filepd&client_id=demoApp&client_secret=demoAppSecret&redirect_uri=http://baidu.com
* 這裏的code字段是授權碼模式中返回的code 例如: https://www.baidu.com/?code=tsuHSh
*
* *【通過code】 換token-標準實現請求中不效驗client_secret參數
* 注:配置com.oath.config.OAuth2ServerConfig&configure(AuthorizationServerSecurityConfigurer oauthServer)中去掉:allowFormAuthenticationForClients()配置即可
* http://localhost:8080/oauth/token?grant_type=authorization_code&code=Filepd&client_id=demoApp&redirect_uri=http://baidu.com
* 這裏的code字段是授權碼模式中返回的code 例如: https://www.baidu.com/?code=tsuHSh
*
* *【通過refresh token】 刷新token
* http://localhost:8080/oauth/token?grant_type=refresh_token&refresh_token=7ba47059-d853-4050-9c64-69d0cade71a7&client_id=demoApp&client_secret=demoAppSecret
* 其中grant_type爲固定值:grant_type=refresh_token, refresh_token = 通過code獲取的token中的refresh_token
*
* *【效驗token】 是否合法
* http://localhost:8080/oauth/check_token?token=7b6efb3e-1b72-4089-81a9-1580a0eb7a70
*
* *【訪問 API】 參數需要access_token
* http://localhost:8080/api/hello/1?access_token=7b6efb3e-1b72-4089-81a9-1580a0eb7a70
*
*/
public static void main(String[] args) {
SpringApplication.run(SecurityOauth2DemoApplication.class, args);
}
}
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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.oath.demo</groupId>
<artifactId>security-oauth2-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>security-oauth2-demo</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>public</id>
<name>aliyun nexus</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
數據庫文件:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(48) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`access_token_validity` int(11) NULL DEFAULT NULL,
`refresh_token_validity` int(11) NULL DEFAULT NULL,
`additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details` VALUES ('demoApp', 'oauth2-resource', '$2a$10$z6DXhwPrzQe4wk9nGmqLvO6zQzYEAYscmNaAaDTDVhhuJGrhqZzk.', 'all', 'authorization_code,client_credentials,password,refresh_token', 'http://baidu.com', 'ROLE_CLIENT', 3600, 3600, NULL, NULL);
SET FOREIGN_KEY_CHECKS = 1;
properties:
spring.datasource.url=jdbc:mysql://localhost:3306/test2?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Redis數據庫索引(默認爲0)
spring.redis.database=0
# Redis服務器地址
spring.redis.host=localhost
# Redis服務器連接端口
spring.redis.port=6379
# Redis服務器連接密碼(默認爲空)
spring.redis.password=
# 連接池最大連接數(使用負值表示沒有限制)
spring.redis.jedis.pool.max-active=8
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.jedis.pool.max-wait=-1
# 連接池中的最大空閒連接
spring.redis.jedis.pool.max-idle=8
# 連接池中的最小空閒連接
spring.redis.jedis.pool.min-idle=0
# 連接超時時間(毫秒)
spring.redis.timeout=5000
logback:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="log.path" value="/home/logs" />
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<!-- 控制檯輸出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 顯示形成的sql、使用的參數、結果集 -->
<!--
<logger name="java.sql" level="debug" />
<logger name="org.springframework.jdbc" level="debug" />
-->
<logger name="com.oath" level="debug" />
<logger name="org.springframework" level="debug" />
<root level="info">
<appender-ref ref="console" />
</root>
</configuration>