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