security和oauth2.0的整合

时间:2022-06-10
本文章向大家介绍security和oauth2.0的整合,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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