授權服務
授權服務(Authorization Server) 應包含對接入端以及登陸用戶的合法性進行驗證並且頒發token等功能,對令牌的請求端點由 spring mvc 控制器進行實現,下面是一個認證服務需要實現的 endpoints:
AuthorizationEndpoint 服務用於認證請求,默認的 URL : /oauth/authorize
TokenEndpoint 服務於訪問令牌的請求 默認URL: /oauth/token
資源服務(ResourceServer) 應包含對資源的保護功能,對非法請求進行攔截,對請求中 token 進行解析鑑權等,下面的過濾器用於實現 OAuth2資源服務
:
OAuth2AuthenticationProcessingFilter 用來對請求給出的身份令牌解析鑑權。
等下我們創建一個類似於下方結構的認證授權
認證流程如下:
1. 客戶端請求uaa授權服務
2. 認證通過後由uaa頒發令牌
3. 客戶端攜帶令牌token 請求資源服務
4. 資源服務校驗令牌的合法性,合法即返回資源信息
環境搭建
初始化數據庫SQL
/*
Navicat MySQL Data Transfer
Target Server Type : MYSQL
Target Server Version : 80013
File Encoding : 65001
Date: 2019-11-12 17:18:59
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for t_permission
-- ----------------------------
DROP TABLE IF EXISTS `t_permission`;
CREATE TABLE `t_permission` (
`id` varchar(32) NOT NULL,
`code` varchar(32) NOT NULL,
`description` varchar(64) DEFAULT NULL,
`url` varchar(128) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_permission
-- ----------------------------
INSERT INTO `t_permission` VALUES ('1', 'p1', '測試資源1', '/r/r1');
INSERT INTO `t_permission` VALUES ('2', 'p3', '測試資源2', '/r/r2');
-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
`id` varchar(32) NOT NULL,
`role_name` varchar(255) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`status` char(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_role_name` (`role_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_role
-- ----------------------------
INSERT INTO `t_role` VALUES ('1', '管理員', null, null, null, '');
-- ----------------------------
-- Table structure for t_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `t_role_permission`;
CREATE TABLE `t_role_permission` (
`role_id` varchar(32) NOT NULL,
`permission_id` varchar(32) NOT NULL,
PRIMARY KEY (`role_id`,`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_role_permission
-- ----------------------------
INSERT INTO `t_role_permission` VALUES ('1', '1');
INSERT INTO `t_role_permission` VALUES ('1', '2');
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` bigint(20) NOT NULL,
`username` varchar(64) NOT NULL,
`password` varchar(255) NOT NULL,
`fullname` varchar(255) NOT NULL,
`moblie` varchar(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES ('1', 'zhangsan', '$2a$10$aFsOFzujtPCnUCUKcozsHux0rQ/3faAHGFSVb9Y.B1ntpmEhjRtru', '張三', '11011912011');
-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (
`user_id` varchar(32) NOT NULL,
`role_id` varchar(32) NOT NULL,
`create_time` datetime DEFAULT NULL,
`creator` varchar(255) DEFAULT NULL,
PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_user_role
-- ----------------------------
INSERT INTO `t_user_role` VALUES ('1', '1', null, null);
項目最終結構
創建maven 父級工程
pop.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.fllday.security</groupId>
<artifactId>distributed-security</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>distributed-security-order</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.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>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.name}</finalName>
<resources>
<resource>
<directory>src/main/resource</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>utf-8</encoding>
<useDefaultDelimiters>true</useDefaultDelimiters>
</configuration>
</plugin>
</plugins>
</build>
</project>
創建子項目 uaa 授權服務中心
pop.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>distributed-security</artifactId>
<groupId>cn.fllday.security</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>distributed-security-uaa</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<!--<exclusions>-->
<!--<exclusion>-->
<!--<groupId>org.springframework.security.oauth.boot</groupId>-->
<!--<artifactId>spring-security-oauth2-autoconfigure</artifactId>-->
<!--</exclusion>-->
<!--</exclusions>-->
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
引入的maven 依賴如下
uua 模塊的項目結構如下
application.properties
spring.application.name=uaa-service
server.port=53020
spring.main.allow-bean-definition-overriding=true
#logging.level.root = debug
#logging.level.org.springframework.web = info
spring.http.encoding.enabled=true
spring.http.encoding.charset=utf-8
spring.http.encoding.force=true
server.tomcat.remote-ip-header=x-forwarded-for
server.tomcat.protocol-header=x-forwarded-proto
server.tomcat.max-http-post-size=5MB
server.use-forward-headers=true
server.servlet.context-path=/uaa
spring.freemarker.enabled=true
spring.freemarker.suffix=.html
spring.freemarker.request-context-attribute=rc
spring.freemarker.content-type=text/html
spring.freemarker.charset=UTF-8
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
spring.datasource.url=jdbc:mysql://ip:port/user_db?useUnicode=true
spring.datasource.username=username
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
management.endpoints.web.exposure.exclude=refresh,health,info,env
feign.hystrix.enabled=true
feign.compression.request.enabled=true
feign.compression.request.mime-types[0]=text/xml
feign.compression.request.mime-types[1]=application/xml
feign.compression.request.mime-types[2]=application/json
feign.compression.request.min-request-size=2048
feign.compression.response.enabled=true
實體類根據SQL文件創建就可以了。一個一個對應起來就ok了。我就不寫了
UserDao.java
@Repository
public class UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
public UserDto getUserByUsername(String usernmae)
{
String sql = "select id,username,password,fullname from t_user where username = ?";
List<UserDto> result = jdbcTemplate.query(sql, new Object[]{usernmae}, new BeanPropertyRowMapper<>(UserDto.class));
if (result== null && result.size() <= 0)return null;
return result.get(0);
}
// 根據用戶id 查詢用戶權限
public List<String> findPermissionByUserId(String userId)
{
String sql = "SELECT * FROM t_permission WHERE id IN (" +
" SELECT permission_id FROM t_role_permission WHERE role_id IN (" +
" SELECT role_id FROM t_user_role WHERE user_id = ? " +
")" +
")";
List<PermissionDto> result = jdbcTemplate.query(sql, new Object[]{userId}, new BeanPropertyRowMapper<>(PermissionDto.class));
List<String> permissions = new ArrayList<String>();
result.forEach( p->permissions.add(p.getCode()) );
return permissions;
}
}
SpringDataUserDetailsService
這個類需要實現 UserDetailsService
@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
UserDto bean = userDao.getUserByUsername(s);
if (bean == null)
return null;
List<String> permissions = userDao.findPermissionByUserId(bean.getId());
String[] permissionArray = new String[permissions.size()];
permissions.toArray(permissionArray);
UserDetails userDetails = User.withUsername(bean.getUsername()).password(bean.getPassword()).authorities(permissionArray).build();
return userDetails;
}
}
下面就是配置類了
TokenConfig
token存儲策略
@Configuration
public class TokenConfig {
// 令牌存儲策略
@Bean
public TokenStore tokenStore()
{
// 內存方式,生成普通令牌
return new InMemoryTokenStore();
}
}
授權服務器配置類
需要使用 @EnableAuthorizationServer
註解並集成 AuthorizationServerConfigurerAdapter
來配置 OAuth2.0
授權服務器
AuthorizationServerConfigurerAdapter
要求配置以下幾個類,這幾個類是由Spring創建的獨立配置對象,他們會被Spring傳入 AuthorizationServerConfigurer
進行配置
ClientDetailsServiceConfigurer:
用來配置客戶端詳情服務(ClientDetailService
),客戶端詳情信息在這裏進行初始化,你可以把客戶端詳情信息寫死在這裏或者通過數據庫來配置,我們這邊寫死在這裏就好了
AuthorizationServerEndpointsConfigurer:
用來配置令牌(token
)的訪問斷電和令牌服務(token services
)
AuthorizationServerSecurityConfigurer:
用來配置令牌端點的安全約束
配置客戶端詳細信息
ClientDetailsServiceConfigurer
負責查找 ClientDetails,而ClientDetails 有幾個重要屬性如表:
屬性名 | 作用 |
---|---|
clientId | 必須的 用來標識客戶端的id |
secret | 需要值得信任的客戶端 , 客戶端安全碼,如果有的話 |
scope | 用來限制客戶端的訪問範圍,如果爲空(默認),那麼客戶端擁有全部的訪問範圍 |
authorizedGrantTypes | 由此客戶端可以使用的授權類型,默認爲空 |
authorities | 此客戶端可以使用的權限(基於SpringSecurity authorities) |
客戶端詳情(client details)能夠在應用程序運行的時候進行更新,可以通過訪問底層的存儲服務,例如將客戶端向高清存儲在一個關係數據庫的表中,你就可以使用jdbcClientDetailsService 或者通過自己實現 ClientDetailsService 來進行管理
管理令牌
AuthorizatonServerTokenService 接口定義了一些操作使你可以進行一些必要的管理,令牌可以被用來加載身份信息。
InMemoryTokenStore:
這個版本是被默認採用的。 我們的授權認證也是基於這個JdbcTokenStore:
這是一個基於JDBC的版本,看名字也知道,使用的時候需要 引入依賴spring-jdbc
JwtTokenStore:
這個版本全程是JSON Web Token (Jwt)
他可以吧令牌相關的數據進行編碼,因此對於後臺來說,他不需要進行存儲,這是一個優勢,但是他也有一個缺點,那就是撤銷一個已經授權的令牌將會非常困難,所以它通常用來處理一個生命週期比較短的令牌以及撤銷刷新令牌(refresh_token)。另一個缺點就是這個令牌佔用的空間比較大,如果你加入了比較多的用戶憑證信息,Jwt不會保存任何數據。
我們這邊選擇內存方式。
配置授權類型(Grant Types)
AuthorizationServerEndpointsConfigurer 通過設定以下屬性支持的授權類型(GrantTypes)
authenticationManager:
認證管理器,當你選擇了資源所有者密碼(password)授權類型的時候,請設置這個屬性注入一個AuthenticationManager對象
userDetailsService
如果你設置這個屬性的話,說明你有一個自己的UserDetailsService接口的實現。
authorizationCodeServices:
這個屬性是用來設置授權碼服務的,主要用於authorization_code
授權碼類型模式
implicatGrantService:
設置隱式授權模式,用來管理隱式授權模式的狀態
toeknGranter
授權將會交給你自己來完全掌控
默認的URL鏈接如下
/**
* /oauth/authorize 授權端點
* /oauth/token 令牌斷點
* /oauth/confirm-access用戶確認授權提交端點
* /auth/error 授權服務錯誤信息斷電
* /auth/check_token 用戶資源服務訪問的令牌解析斷電
* /oauth/token_key 提供公有密鑰的端點,如果你使用jwt令牌的話
*/
需要注意的是這個授權端點URL應該被SpringSecurity保護起來只提供給授權用戶訪問
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Bean
public AuthorizationCodeServices authorizationCodeServices()
{
return new InMemoryAuthorizationCodeServices();
}
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private ClientDetailsService clientDetailsService;
// 配置客戶端詳細信息
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory() // 使用內存存儲
.withClient("c1") // client_id
// 客戶端密鑰
.secret(new BCryptPasswordEncoder().encode("secret"))
// 資源列表
.resourceIds("res1")
// 授權方式
.authorizedGrantTypes("authorization_code","password","client_creentials","implicit","refresh_token")
// 允許授權的範圍
.scopes("all")
//
.autoApprove(false) // false 跳轉到授權頁面
// 加上驗證回調地址
.redirectUris("http://www.baidu.com");
}
// 令牌管理服務
public AuthorizationServerTokenServices tokenServices()
{
DefaultTokenServices service = new DefaultTokenServices();
service.setClientDetailsService(clientDetailsService); // 客戶端信息服務
service.setSupportRefreshToken(true); // 是否產生刷新令牌
service.setTokenStore(tokenStore); // 設置令牌存儲策略
service.setAccessTokenValiditySeconds(7200);// 令牌默認有效期 2 小時
service.setRefreshTokenValiditySeconds(259200);
return service;
}
/**
* /oauth/authorize 授權端點
* /oauth/token 令牌斷點
* /oauth/confirm-access用戶確認授權提交端點
* /auth/error 授權服務錯誤信息斷電
* /auth/check_token 用戶資源服務訪問的令牌解析斷電
* /oauth/token_key 提供公有密鑰的端點,如果你使用jwt令牌的話
*/
/**
* 令怕i訪問端點
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
// 密碼管理模式
.authenticationManager(authenticationManager)
// 授權碼模式
.authorizationCodeServices(authorizationCodeServices)
.tokenServices(tokenServices()) // 令牌管理服務
.allowedTokenEndpointRequestMethods(HttpMethod.POST); // 允許post提交
}
/**
* 配置令牌端點(Token Endpoint)的安全約束
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()") // /oauth/token_key 公開
.checkTokenAccess("permitAll()") // /auth/check_token 檢測令牌
.allowFormAuthenticationForClients(); // 允許通過表單認證,申請令牌
super.configure(security);
}
}
授權服務配置總結
授權服務分成三大塊,可以關聯記憶。
既然要完成,它首先得知道客戶端從哪裏讀取,因此要進行客戶端詳情配置,既然要頒發token,那麼必須要定義 token的相關 endpoint , 以及 token 如何存取,以及客戶端支持那些類型的 token。
既然暴露了一些 endpoint, 那對這些 endpoint 可以定義一些安全上的約束等。
Web 安全配置
配置 WebSecirutyConfig.java
, 繼承WebSecurityConfigurerAdapter
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//認證管理器
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 安全攔截機制
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/r/r1").hasAnyAuthority("p1")
.antMatchers("/login*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
;
}
}
配置啓動類
UaaServer.java
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableFeignClients(basePackages = {"cn.fllday.security.distruibuted.uaa"})
public class UaaServer {
public static void main(String[] args) {
SpringApplication.run(UaaServer.class);
}
}
到這裏我們的授權認證中心就已經完成了。接下來配置一個客戶端。
創建一個order
的模塊
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.fllday.security</groupId>
<artifactId>distributed-security-order</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<artifactId>distributed-security</artifactId>
<groupId>cn.fllday.security</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
引入的依賴如下
application.properties
文件
spring.application.name=order-service
server.port=53022
spring.main.allow-bean-definition-overriding=true
logging.level.root=debug
logging.level.org.springframework.web=info
spring.http.encoding.enabled=true
spring.http.encoding.charset=utf-8
spring.http.encoding.force=true
server.tomcat.remote-ip-header=x-forwarded-for
server.tomcat.protocol-header=x-forwarded-proto
server.use-forward-headers=true
server.servlet.context-path=/order
spring.freemarker.enabled=true
spring.freemarker.suffix=.html
spring.freemarker.request-context-attribute=rc
spring.freemarker.content-type=text/html
spring.freemarker.charset=utf-8
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
資源服務器配置
@EnableResourceServer
註解到一個@Configuration
配置類上,並且必須使用ResourceServerConfigurer
這個配置對象來進行配置。 下面是一些可以配置的屬性
tokenServices
:ResourceServerTokenServices 類的實例,用來實現令牌服務tokenStore
:TokenStore類的實力,制定令牌如何訪問,與tokenServices 配置可選resourceId
: 這個資源服務器的id,這個屬性是可選的,但是推薦設置並在授權服務中進行驗證- 其他的拓展例
tokenExtractor
令牌提取器用來提取請求中的令牌
HttpSecurity
配置這個與SpringSecurity
類似
- 請求匹配器,用來設置需要進行保護的資源路徑,默認的情況下是保護資源服務器全部路徑。
- 通過http.authorizeRequests() 來設置受保護資源的訪問規則
- 其他的自定義權限保護規則通過 httpSecurity 來進行配置
@EnableResourceServer
註解自動增加了一個類型爲Oauth2AuthenticationProcessingFilter
的過濾器鏈
ResourceServerConfig.java
package cn.fllday.security.distributed.order.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "res1";
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").access("#oauth2.hasScope('all')")
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
super.configure(http);
}
// 資源服務令牌解析服務
/**
* 使用遠程服務請求授權服務器校驗的token,必須制定校驗token 的url, client_id,client_secret
* @return
*/
@Bean
public ResourceServerTokenServices tokenServices()
{
RemoteTokenServices service = new RemoteTokenServices();
service.setCheckTokenEndpointUrl("http://localhost:53020/uaa/oauth/check_token");
service.setClientId("c1");
service.setClientSecret("secret");
return service;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID) // 資源id
.tokenServices(tokenServices()) // 驗證令牌的服務
.stateless(true);
}
}
添加安全訪問控制
WebSecurityConfig.java
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/r/**").authenticated() // 表示 /r/的路徑都會受到保護
.anyRequest().permitAll();
}
}
創建資源接口
OrderController.java
@RestController
@RequestMapping(value = "/r")
public class OrderController {
@GetMapping(value = "/r1")
@PreAuthorize("hasAnyAuthority('p1')") // 擁有p1權限方可發個文
public String r1()
{
return "訪問資源1";
}
@GetMapping(value = "/r2")
@PreAuthorize("hasAnyAuthority('p2')") // 擁有p2權限方可發個文
public String r2()
{
return "訪問資源2";
}
}
配置啓動類
Order.java
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServer {
public static void main(String[] args) {
SpringApplication.run(OrderServer.class,args);
}
}
順序啓動 uaa 和 order
測試
這裏使用的工具是postman
我們先嚐試訪問獲取token的接口
這裏的參數都是哪裏來的呢?
發現原來在我們的AuthorizationServer類中configure方法中ClientDetailsServiceConfigurer 配置過了
那麼code 哪裏來的呢?
我們使用瀏覽器訪問http://localhost:53020/uaa/login
有一個登陸頁面。我們使用數據庫中添加的用戶名和密碼,如果你用的我的sql文件的話,用戶名 zhangsan, 密碼 123。登陸成功後訪問http://localhost:53020/uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com
, 出現一個頁面爲
點擊Authorize
你的頁面就跳轉到百度了。 但是瀏覽器後面會跟上一串字符串
訪問token接口的code 參數的值就是這麼來的
訪問獲取token的接口
訪問資源
不懈怠token 就會報錯,總之就是不同意你訪問這個資源
注意看我圖裏綠色圈圈起來的地方。token 需要加上 Bearer
有個空格哦!!
訪問沒有資源的接口
下面我們去看看那個校驗token的接口做了什麼
上面的不用解釋了吧!
至於其他的模式。等下次在挨個解釋吧。 下一篇文章會使用jwt,以及通過 springcloud 的網關來控制。