security和oauth2.0的整合
之前已經介紹過security的相關的介紹,現在所需要做的就是security和oauth2.0的整合,在原有的基礎上我們加上一些相關的代碼;代碼實現如下:
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>urity.demo</groupId>
<artifactId>security-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!--以下兩項需要如果不配置,解析themleaft 會有問題-->
<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.0.5</thymeleaf-layout-dialect.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>Brussels-SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<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>
<scope>test</scope>
</dependency>
<!--mybatis與mysql-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid依賴-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.25</version>
</dependency>
<!--redis依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--jasypt加解密-->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>1.14</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<!--oauth2.0-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<!--session集群管理-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!--zipkin-->
<!-- <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>-->
<!--eureka-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--添加static和templates的依賴-->
<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>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--config-->
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-config</artifactId>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-bus-amqp</artifactId>-->
<!--</dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
這里我們需要注意導入依賴的版本,版本過高可能會存在一些未知的問題:
AuthorizationServerConfiguration核心類:
package urity.demo.oauth2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
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.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import urity.demo.service.RedisAuthenticationCodeServices;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Value("${resource.id:spring-boot-application}")
private String resourceId;
@Value("${access_token.validity_period:36000}")
private int accessTokenValiditySeconds = 36000;
//認證管理 很重要 如果security版本高可能會出坑哦
@Resource
private AuthenticationManager authenticationManager;
@Resource
private RedisAuthenticationCodeServices redisAuthenticationCodeServices;
//定義令牌端點上的安全約束。
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')");
oauthServer.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
}
//將ClientDetailsServiceConfigurer(從您的回調AuthorizationServerConfigurer)可以用來在內存或JDBC實現客戶的細節服務來定義的。客戶端的重要屬性是
//clientId:(必填)客戶端ID。
//secret:(可信客戶端需要)客戶機密碼(如果有)。
//scope:客戶受限的范圍。如果范圍未定義或為空(默認值),客戶端不受范圍限制。
//authorizedGrantTypes:授予客戶端使用授權的類型。默認值為空。
//authorities授予客戶的授權機構(普通的Spring Security權威機構)。
//客戶端的詳細信息可以通過直接訪問底層商店(例如,在數據庫表中JdbcClientDetailsService)或通過ClientDetailsManager接口(這兩種實現ClientDetailsService也實現)來更新運行的應用程序。
//注意:JDBC服務的架構未與庫一起打包(因為在實踐中可能需要使用太多變體)
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//默認值InMemoryTokenStore對於單個服務器是完全正常的(即,在發生故障的情況下,低流量和熱備份備份服務器)。大多數項目可以從這里開始,也可以在開發模式下運行,以便輕松啟動沒有依賴關系的服務器。
//這JdbcTokenStore是同一件事的JDBC版本,它將令牌數據存儲在關系數據庫中。如果您可以在服務器之間共享數據庫,則可以使用JDBC版本,如果只有一個,則擴展同一服務器的實例,或者如果有多個組件,則授權和資源服務器。要使用JdbcTokenStore你需要“spring-jdbc”的類路徑。
clients.inMemory()
//client Id
.withClient("normal-app")
.authorizedGrantTypes("authorization_code", "implicit")
.authorities("ROLE_CLIENT")
.scopes("read","write")
.resourceIds(resourceId)
.accessTokenValiditySeconds(accessTokenValiditySeconds)
.and()
.withClient("trusted-app")
.authorizedGrantTypes("client_credentials", "password")
.authorities("ROLE_TRUSTED_CLIENT")
.scopes("read", "write")
.resourceIds(resourceId)
.accessTokenValiditySeconds(accessTokenValiditySeconds)
.secret("secret");
}
//AuthorizationEndpoint可以通過以下方式配置支持的授權類型AuthorizationServerEndpointsConfigurer。默認情況下,所有授權類型均受支持,除了密碼(有關如何切換它的詳細信息,請參見下文)。以下屬性會影響授權類型:
//authenticationManager:通過注入密碼授權被打開AuthenticationManager。
//userDetailsService:如果您注入UserDetailsService或者全局配置(例如a GlobalAuthenticationManagerConfigurer),則刷新令牌授權將包含對用戶詳細信息的檢查,以確保該帳戶仍然活動
//authorizationCodeServices:定義AuthorizationCodeServices授權代碼授權的授權代碼服務(實例)。
//implicitGrantService:在批准期間管理狀態。
//tokenGranter:(TokenGranter完全控制授予和忽略上述其他屬性)
//在XML授予類型中包含作為子元素authorization-server。
/**
* /oauth/authorize您可以從該請求中獲取所有數據,
* 然后根據需要進行渲染,
* 然后所有用戶需要執行的操作都是回復有關批准或拒絕授權的信息。
* 請求參數直接傳遞給您UserApprovalHandler,
* AuthorizationEndpoint所以您可以隨便解釋數據
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager);
endpoints.accessTokenConverter(accessTokenConverter());//jwt
endpoints.tokenStore(tokenStore());
//授權碼存儲
endpoints.authorizationCodeServices(redisAuthenticationCodeServices);
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {
/**
* 重寫增強token的方法
* 自定義返回相應的信息
*
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String userName = authentication.getUserAuthentication().getName();
// 與登錄時候放進去的UserDetail實現類一直查看link{SecurityConfiguration}
User user = (User) authentication.getUserAuthentication().getPrincipal();
/** 自定義一些token屬性 ***/
final Map<String, Object> additionalInformation = new HashMap<>();
additionalInformation.put("userName", userName);
additionalInformation.put("roles", user.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);
return enhancedToken;
}
};
// 測試用,資源服務使用相同的字符達到一個對稱加密的效果,生產時候使用RSA非對稱加密方式
accessTokenConverter.setSigningKey("123");
return accessTokenConverter;
}
@Bean
public TokenStore tokenStore() {
TokenStore tokenStore = new JwtTokenStore(accessTokenConverter());
return tokenStore;
}
}
RedisAuthenticationCodeServices:
我們把授權碼存在了redis中:
package urity.demo.service;
import lombok.extern.slf4j.Slf4j;
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.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.security.oauth2.common.util.SerializationUtils;
//自定義為使用redis存儲授權碼
@Service
@Slf4j
public class RedisAuthenticationCodeServices extends RandomValueAuthorizationCodeServices {
private static final String AUTH_CODE_KEY = "my_code";
private RedisConnectionFactory connectionFactory;
public RedisAuthenticationCodeServices(RedisConnectionFactory connectionFactory) {
Assert.notNull(connectionFactory, "RedisConnectionFactory required");
this.connectionFactory = connectionFactory;
}
private RedisConnection getConnection() {
return connectionFactory.getConnection();
}
//redis存儲
@Override
protected void store(String code, OAuth2Authentication authentication) {
RedisConnection conn = getConnection();
try {
conn.hSet(AUTH_CODE_KEY.getBytes("utf-8"), code.getBytes("utf-8"),
SerializationUtils.serialize(authentication)
);
} catch (Exception e) {
conn.close();
}
}
@Override
protected OAuth2Authentication remove(String code) {
RedisConnection conn = getConnection();
try {
OAuth2Authentication authentication = null;
try {
authentication = SerializationUtils
.deserialize(conn.hGet(AUTH_CODE_KEY.getBytes("utf-8"),
code.getBytes("utf-8")));
} catch (Exception e) {
e.printStackTrace();
}
if (authentication != null) {
conn.hDel(AUTH_CODE_KEY.getBytes("utf-8"),
code.getBytes("utf-8"));
}
return authentication;
} catch (Exception e) {
e.printStackTrace();
} finally {
conn.close();
}
return null;
}
}
ResourceController:
package urity.demo.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/***
* 受保護的資源服務
* @author leftso
*
*/
@RestController
@RequestMapping("/resources")
public class ResourceController {
/**
* 需要用戶角色權限
* @return
*/
@PreAuthorize("hasRole('ROLE_USER')")
@RequestMapping(value="user", method=RequestMethod.GET)
public String helloUser() {
return "hello user";
}
/**
* 需要管理角色權限
*
* @return
*/
@PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping(value="admin", method=RequestMethod.GET)
public String helloAdmin() {
return "hello admin";
}
/**
* 需要客戶端權限
*
* @return
*/
@PreAuthorize("hasRole('ROLE_CLIENT')")
@RequestMapping(value="client", method=RequestMethod.GET)
public String helloClient() {
return "hello user authenticated by normal client";
}
/**
* 需要受信任的客戶端權限
*
* @return
*/
@PreAuthorize("hasRole('ROLE_TRUSTED_CLIENT')")
@RequestMapping(value="trusted_client", method=RequestMethod.GET)
public String helloTrustedClient() {
return "hello user authenticated by trusted client";
}
@RequestMapping(value="principal", method=RequestMethod.GET)
public Object getPrincipal() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return principal;
}
@RequestMapping(value="roles", method=RequestMethod.GET)
public Object getRoles() {
return SecurityContextHolder.getContext().getAuthentication().getAuthorities();
}
}
application.xml:
server:
port: 8787
spring:
redis:
host: 127.0.0.1
port: 6379
# password: redis
database: 0
datasource:
url: jdbc:mysql://localhost:3306/test
username: ***
password: ***
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 30
maxWait: 10000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMills: 300000
session:
store-type: none
other:
security:
oauth2:
signKey: oauth