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版本高可能會出坑哦@Resourceprivate AuthenticationManager authenticationManager;@Resourceprivate RedisAuthenticationCodeServices redisAuthenticationCodeServices;//定義令牌端點上的安全約束。@Overridepublic 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服務的架構未與庫一起打包(因為在實踐中可能需要使用太多變體)@Overridepublic 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*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(this.authenticationManager);endpoints.accessTokenConverter(accessTokenConverter());//jwtendpoints.tokenStore(tokenStore());//授權碼存儲endpoints.authorizationCodeServices(redisAuthenticationCodeServices);}@Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {/*** 重寫增強token的方法* 自定義返回相應的信息**/@Overridepublic 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;}@Beanpublic 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存儲@Overrideprotected 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();}}@Overrideprotected 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.1port: 6379
# password: redisdatabase: 0datasource:url: jdbc:mysql://localhost:3306/testusername: ***password: ***driver-class-name: com.mysql.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourceinitialSize: 5minIdle: 5maxActive: 30maxWait: 10000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMills: 300000session:store-type: none
other:security:oauth2:signKey: oauth