前言:本文是OAuth2.0實踐篇,閱讀之前需要先掌握OAuth2.0基本原理,原理介紹見:OAuth2.0入門(一)—— 基本概念詳解和圖文並茂講解四種授權類型
本章將採用微服務架構方式,將OAuth2-Demo拆分成三個模塊:oauth2-authentication-server(作爲授權認證中心)、oauth2-resource-server(作爲資源服務器)、oauth-client(作爲第三方應用,模擬如何獲取Token訪問資源)。
一、項目總結構
其中oauth2-demo是其他模塊的Parent模塊,定義了一些通用的Jar包。完整的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.scb</groupId>
<artifactId>oauth2-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>oauth2-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.11</version>
</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>
<modules>
<module>oauth2-authentication-server</module>
<module>oauth2-resource-server</module>
<module>oauth-client</module>
</modules>
</project>
二、oauth2-authentication-server 模塊
oauth2-authentication-server 模塊是作爲全局的授權認證中心,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>com.scb</groupId>
<artifactId>oauth2-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>oauth2-authentication-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>oauth2-authentication-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- 阿里系的Druid依賴包 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<!-- Druid 依賴 log4j包 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<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.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
這裏,除了導入spring-boot-starter-security和spring-security-oauth2認證框架外,還需要使用H2內存數據庫來存儲用戶和角色信息及OAuth2的表。
先來看看application.yml文件:
spring:
h2:
console:
path: /h2-console
enabled: true
settings:
web-allow-others: true
jpa:
generate-ddl: false
show-sql: true
hibernate:
ddl-auto: none
datasource:
platform: h2
schema: classpath:schema.sql
data: classpath:data.sql
url: jdbc:h2:~/auth;AUTO_SERVER=TRUE
username: sa
password:
type: com.alibaba.druid.pool.DruidDataSource
druid:
min-idle: 2
initial-size: 5
max-active: 10
max-wait: 5000
validation-query: select 1
resources:
static-locations: classpath:/templates/,classpath:/static/
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mode: HTML5
servlet:
content-type: text/html
cache: false
server:
port: 8080
logging:
pattern:
level: debug
在yml文件中,我們定義了datasource爲H2,並指定了schema、data文件,這樣在項目運行時會執行相應的sql。其中schema.sql文件如下:
/* 1、存放用戶認證信息及權限 */
drop table if exists authority;
CREATE TABLE authority (
id integer,
authority varchar(255),
primary key (id)
);
drop table if exists credentials;
CREATE TABLE credentials (
id integer,
enabled boolean not null,
name varchar(255) not null,
password varchar(255) not null,
version integer,
primary key (id)
);
drop table if exists credentials_authorities;
CREATE TABLE credentials_authorities (
credentials_id bigint not null,
authorities_id bigint not null
);
/* 2、oauth2官方建表語句 */
drop table if exists oauth_client_token;
create table oauth_client_token (
token_id VARCHAR(255),
token LONGBLOB,
authentication_id VARCHAR(255),
user_name VARCHAR(255),
client_id VARCHAR(255)
);
drop table if exists oauth_client_details;
CREATE TABLE oauth_client_details (
client_id varchar(255) NOT NULL,
resource_ids varchar(255) DEFAULT NULL,
client_secret varchar(255) DEFAULT NULL,
scope varchar(255) DEFAULT NULL,
authorized_grant_types varchar(255) DEFAULT NULL,
web_server_redirect_uri varchar(255) DEFAULT NULL,
authorities varchar(255) DEFAULT NULL,
access_token_validity integer(11) DEFAULT NULL,
refresh_token_validity integer(11) DEFAULT NULL,
additional_information varchar(255) DEFAULT NULL,
autoapprove varchar(255) DEFAULT NULL
);
drop table if exists oauth_access_token;
create table `oauth_access_token` (
token_id VARCHAR(255),
token LONGBLOB,
authentication_id VARCHAR(255),
user_name VARCHAR(255),
client_id VARCHAR(255),
authentication LONGBLOB,
refresh_token VARCHAR(255)
);
drop table if exists oauth_refresh_token;
create table `oauth_refresh_token`(
token_id VARCHAR(255),
token LONGBLOB,
authentication LONGBLOB
);
drop table if exists oauth_code;
create table oauth_code (
code VARCHAR(255),
authentication BLOB
);
drop table if exists oauth_approvals;
create table oauth_approvals (
userId VARCHAR(255),
clientId VARCHAR(255),
scope VARCHAR(255),
status VARCHAR(10),
expiresAt DATETIME,
lastModifiedAt DATETIME
);
這裏分爲兩部分,第一部分是自定義的用於存放用戶憑證及授權的表。第二部分是官方建表語句:spring-security-oauth schema.sql,各個數據表說明如下:
- oauth_client_details:存放client信息,主要操作類爲JdbcClientDetailsService。
- oauth_client_token:存放client的Token信息。即通過client_credentials授權方式獲得的Token。主要操作類爲JdbcClientTokenServices。
- oauth_access_token:存放access token等,主要操作類爲JdbcTokenStore。
- oauth_refresh_token:跟oath_access_token表類似,當client的grant type支持refresh token時纔有記錄。主要操作類爲JdbcTokenStore。
- oauth_code:存放授權碼(Authorization Code),即當client的grant type支持authorization_code時纔有記錄。主要操作類爲JdbcAuthorizationCodeServices。
- oauth_approvals:存放用戶授權client的信息,即當client的grant type支持authorization_code時纔有記錄。主要操作類爲JdbcApprovalStore。
字段及表詳細說明如下,圖片來源:https://blog.csdn.net/qq_34997906/article/details/89609297
再來看一下data.sql文件,該文件主要是創建一些初始數據。
INSERT INTO authority VALUES(1,'ROLE_OAUTH_ADMIN');
INSERT INTO authority VALUES(2,'ROLE_RESOURCE_ADMIN');
INSERT INTO authority VALUES(3,'ROLE_PRODUCT_ADMIN');
/* password ==> password */
INSERT INTO credentials VALUES(1,true,'oauth_admin','$2a$10$5ze/vcFOsQBF1og.s.eQ0.8VdsUXh7zzul8VM0Dzcq/NKVNrD8ffO','0');
INSERT INTO credentials VALUES(2,true,'resource_admin','$2a$10$5ze/vcFOsQBF1og.s.eQ0.8VdsUXh7zzul8VM0Dzcq/NKVNrD8ffO','0');
INSERT INTO credentials VALUES(3,true,'product_admin','$2a$10$5ze/vcFOsQBF1og.s.eQ0.8VdsUXh7zzul8VM0Dzcq/NKVNrD8ffO','0');
INSERT INTO credentials_authorities VALUES (1,1);
INSERT INTO credentials_authorities VALUES (2,2);
INSERT INTO credentials_authorities VALUES (3,3);
/* password ==> password */
INSERT INTO oauth_client_details VALUES('curl_client','product_api', '$2a$10$5ze/vcFOsQBF1og.s.eQ0.8VdsUXh7zzul8VM0Dzcq/NKVNrD8ffO', 'read,write', 'client_credentials', 'http://localhost:7001/oauth2/accessToken', 'ROLE_PRODUCT_ADMIN', 7200, 0, null, 'true');
INSERT INTO oauth_client_details VALUES ('client_code','product_api','$2a$10$5ze/vcFOsQBF1og.s.eQ0.8VdsUXh7zzul8VM0Dzcq/NKVNrD8ffO','read,write','authorization_code,refresh_token','http://localhost:7001/oauth2/code','ROLE_PRODUCT_ADMIN',7200,72000,null,'true');
INSERT INTO oauth_client_details VALUES ('client_implicit', 'product_api' ,'$2a$10$5ze/vcFOsQBF1og.s.eQ0.8VdsUXh7zzul8VM0Dzcq/NKVNrD8ffO', 'read,write' ,'implicit', 'http://localhost:7001/oauth2/accessToken','ROLE_PRODUCT_ADMIN',7200,72000,null,'true');
因爲項目使用了BCryptPasswordEncoder加密器,所以數據庫的密碼統一加密存儲。
用戶名 | 密碼 | 權限 |
oauth_admin | password | ROLE_OAUTH_ADMIN |
resource_admin | password | ROLE_RESOURCE_ADMIN |
product_admin | password | ROLE_PRODUCT_ADMIN |
接下來是Spring Security的配置部分。
1、Entity層
package com.scb.oauth2authenticationserver.entity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import javax.persistence.*;
import java.io.Serializable;
@Data
@Entity
@Table(name = "authority")
public class Authority implements GrantedAuthority, Serializable {
private static final long serialVersionUID = -4737795841774495818L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "AUTHORITY")
private String authority;
}
package com.scb.oauth2authenticationserver.entity;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
@Data
@Entity
@Table(name = "credentials")
public class Credentials implements Serializable {
private static final long serialVersionUID = -1408491858754963752L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "enabled")
private boolean enabled;
@Column(name = "name",nullable = false)
private String name;
@Column(name = "password",nullable = false)
private String password;
@Version
@Column(name = "version",nullable = false)
private Integer version;
@ManyToMany(fetch = FetchType.EAGER)
private List<Authority> authorities;
}
Entity層是實體層,映射數據表的字段。其中@Version註解是JPA實現的樂觀鎖機制。
2、Repository層
package com.scb.oauth2authenticationserver.repository;
import com.scb.oauth2authenticationserver.entity.Credentials;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CredentialsRepository extends JpaRepository<Credentials,Long> {
Credentials findByName(String name);
}
3、配置UserDetailsService
package com.scb.oauth2authenticationserver.service;
import com.scb.oauth2authenticationserver.entity.Credentials;
import com.scb.oauth2authenticationserver.repository.CredentialsRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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;
@Slf4j
@Service
public class JdbcUserDetailsService implements UserDetailsService {
@Autowired
private CredentialsRepository credentialsRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Credentials credentials = credentialsRepository.findByName(username);
if (credentials == null){
throw new UsernameNotFoundException("User "+username+" cannot be found");
}
User user = new User(credentials.getName(),credentials.getPassword(),credentials.isEnabled(),true,true,true,credentials.getAuthorities());
return user;
}
}
UserDetailsService用於做Spring Security登錄認證。關於Spring Security認證流程見:Spring Security 認證流程詳解
4、SpringSecurityConfig
package com.scb.oauth2authenticationserver.config;
import com.scb.oauth2authenticationserver.service.JdbcUserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
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.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return new JdbcUserDetailsService();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**","/js/**","/fonts/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// .addFilter()
// .antMatcher("oauth/authorize")
.authorizeRequests()
.antMatchers("/login","/logout.do").permitAll()
.antMatchers("/**").authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login.do")
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/login")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout.do"))
.and()
.userDetailsService(userDetailsServiceBean());
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean())
.passwordEncoder(passwordEncoder());
}
}
SpringSecurityConfig模塊一共有3個configure,分別是認證相關的AuthenticationManagerBuilder和web相關的WebSecurity、HttpSecurity。
- AuthenticationManagerBuilder:用來配置全局的認證相關的信息,其實就是AuthenticationProvider和UserDetailsService。
- WebSecurity:用來配置全局請求忽略規則(比如靜態文件)、全局HttpFirewall、是否debug、全局SecurityFilterChain等。
- HttpSecurity:用來配置具體的權限控制規則
5、Controller層
package com.scb.oauth2authenticationserver.controller;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.approval.Approval;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
public class LoginController {
@Autowired
private JdbcClientDetailsService clientDetailsService;
@Autowired
private ApprovalStore approvalStore;
@Autowired
private TokenStore tokenStore;
@InitBinder
protected void init(HttpServletRequest request, ServletRequestDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
@RequestMapping("/login")
public String loginPage() {
tokenStore.findTokensByClientId("client_code").stream().forEach(accessToken -> log.info(accessToken.toString()));
return "login";
}
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logoutPage(HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return "redirect:/login?logout";
}
@RequestMapping("/")
public ModelAndView root(Map<String, Object> model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
log.info(authentication.getName());
List<ClientDetails> clientDetails = clientDetailsService.listClientDetails();
clientDetails.stream().forEach(clientDetails1 -> log.info(clientDetails1.toString()));
List<Approval> approvals = clientDetails.stream()
.map(clientDetails1 -> approvalStore.getApprovals(authentication.getName(), clientDetails1.getClientId()))
.flatMap(Collection::stream)
.collect(Collectors.toList());
approvals.stream().forEach(approval -> log.info(approval.toString()));
model.put("approvals", approvals);
model.put("clientDetails", clientDetails);
return new ModelAndView("index", model);
}
@RequestMapping(value = "/approval/revoke", method = RequestMethod.POST)
public String revokeApproval(@ModelAttribute Approval approval) {
log.info(approval.toString());
Boolean bool = approvalStore.revokeApprovals(Arrays.asList(approval));
log.info(bool.toString());
tokenStore.findTokensByClientIdAndUserName(approval.getClientId(), approval.getUserId())
.forEach(tokenStore::removeAccessToken);
return "redirect:/";
}
}
LoginController定義瞭如下四個API:
- /login:首先,通過TokenStore把client_id爲client_code的access token打印出來(只是試試TokenStore的功能 :) ),然後返回用戶的登錄界面(“login”)。
- /logout:從SecurityContext取用戶的認證信息,若爲空則直接logout,否則重定向到“/login?logout”界面(Spring默認的退出界面)。
- /:根目錄,通過ClientDetailsService查找所有的Client信息,再通過ApprovalStore查找當前登錄用戶所授權的所有的Client的Approval信息,最後封裝進model裏面,供View解析渲染。
- /approval/revoke:該API通過ApprovalStore刪除一個Approval信息,然後通過TokenStore刪除通過該Approval獲得的Token,最後重定向到根目錄。
@InitBinder註解用於SpringMVC表單類型轉換(比如這裏對日期格式做格式化),具體轉換在editor層,代碼就不列出來了。有關@InitBinder註解的更多知識見:SpringMVC註解@initbinder解決類型轉換問題
重頭戲:OAuth2配置部分
package com.scb.oauth2authenticationserver.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetailsService;
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.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import lombok.extern.slf4j.Slf4j;
/*
Authorization Server Config
*/
@Slf4j
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
@Qualifier("jdbcUserDetailsService")
private UserDetailsService userDetailsService;
// @Autowired
// private AuthenticationManager authenticationManager;
public AuthServerConfig() {
super();
}
/*
oauth_access_token Table
*/
@Bean
public TokenStore tokenStore() {
JdbcTokenStore tokenStore = new JdbcTokenStore(dataSource);
log.info("Create TokenStore :: " + tokenStore);
return tokenStore;
}
/*
oauth_client_details Table
用於配置client信息
*/
@Bean
public JdbcClientDetailsService jdbcClientDetailsService() {
JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
log.info("Create ClientDetailsService :: " + clientDetailsService);
return clientDetailsService;
}
/*
ApprovalStore:用於保存、檢索user approval
oauth_approvals Table
*/
@Bean
public ApprovalStore approvalStore() {
JdbcApprovalStore approvalStore = new JdbcApprovalStore(dataSource);
log.info("Create ApprovalStore :: " + approvalStore);
return approvalStore;
}
/*
oauth_code Table
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
JdbcAuthorizationCodeServices authorizationCodeServices = new JdbcAuthorizationCodeServices(dataSource);
log.info("Create AuthorizationCodeServices :: " + authorizationCodeServices);
return authorizationCodeServices;
}
/*
AuthorizationServerSecurityConfigurer:用來配置令牌端點(Token Endpoint)的安全約束
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients()
.checkTokenAccess("permitAll()");
}
/*
ClientDetailsServiceConfigurer:用來配置客戶端詳情服務
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(jdbcClientDetailsService());
}
/*
AuthorizationServerEndpointsConfigurer:來配置授權(authorization)以及令牌(token)的訪問端點和令牌服務(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.approvalStore(approvalStore())
.userDetailsService(userDetailsService)
//.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices())
.tokenStore(tokenStore());
}
}
授權服務器配置:使用 @EnableAuthorizationServer 來配置授權服務機制,並繼承 AuthorizationServerConfigurerAdapter 該類重寫 configure 方法定義授權服務器策略。
TokenStore總共有四種:
- InMemoryTokenStore:將Token信息存入內存中,是OAuth2的默認實現方式。
- JdbcTokenStore:將Token信息存入數據庫
- JwtTokenStore:將相關信息數據存入JWT中,實現無狀態。需要引入 spring-security-jwt 庫。通過JwtAccessTokenConverter進行編碼及解碼,同時需要添加jwtSigningKey,以此生成祕鑰、進行簽名。
- RedisTokenStore:將Token信息存入Redis中
下面在列出OAuth2的一些默認端點:
- /oauth/authorize:授權端點,用於grant_type爲Authorization Code時,獲取授權碼。
- /oauth/token:令牌端點,用於獲取Access Token。
- /oauth/confirm_access:用於grant_type爲Authorization Code時,用戶確認授權提交端點。
- /oauth/error:授權服務錯誤信息端點。
- /oauth/check_token:用於資源服務訪問的令牌解析端點。
- /oauth/token_key:在JwtTokenStore模式下提供公有密匙的端點。
其他
本文篇幅較長,故先只講解了oauth2-authentication-server模塊。剩下的內容見下一篇文章。
本文是基於springboot+springsecurity+oauth2整合(並用mysql數據庫實現持久化客戶端數據)的教程上進行二次開發的
下載項目:https://download.csdn.net/download/qq_37771475/12054521