SpringSecurity -- 授权-OAuth2 --

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 针对之前的授权做个补充,这里集成OAuth2来实现目前支持5中方式,inMemory,jdbc,redis,jwt,jwk......

前言

针对之前的授权做个补充,这里集成OAuth2来实现

目前支持5种方式,inMemory,jdbc,redis,jwt,jwk

对于公司内部使用呢,主推 jwt > redis > jdbc > inMemory

​ 。。。jwk 没玩明白,但是安全性挺高。。。整明白了,觉得可以主推!!!

而认证类型呢,也有5种,authorization_code,refresh_token,password,implicit,client_credentials

对于公司内部,主推 password > authorization_code,

refresh_token一般都是会采用的,所以不区分优先级,后面两种非常的不安全,直接不推荐了

授权服务器-核心代码

0. 在启动类上加注解

@SpringBootApplication
@EnableAuthorizationServer // 开启授权服务器注解
public class AuthApplication {
   
    public static void main(String[] args) {
   
        SpringApplication.run(AuthApplication.class);
    }
}

1. SecurityConfig

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
   

    @Bean
    PasswordEncoder passwordEncoder() {
   
        return new BCryptPasswordEncoder();
    }

    @Override
    @Bean // 暴漏 manager 给其它类使用
    protected AuthenticationManager authenticationManager() throws Exception {
   
        return super.authenticationManager();
    }

    @Bean
    public UserDetailsService userDetailsService() {
   
        InMemoryUserDetailsManager user = new InMemoryUserDetailsManager();
        // 使用内存认证,必须指定角色(roles),实际使用中,肯定是需要自定义对接数据库的,这里为了演示
        user.createUser(User.withUsername("a").password(passwordEncoder().encode("123")).roles("ADMIN").build());
        return user;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   
        auth.userDetailsService(userDetailsService());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
   
        http.authorizeRequests().anyRequest().authenticated()
                .and().formLogin()
                .and().csrf().disable();
    }
}

ps: 以下4中Config只能同时采用1种,切记
ps: 以下4中Config只能同时采用1种,切记
ps: 以下4中Config只能同时采用1种,切记

2. InMemoryAuthServerConfig

@Configuration
public class InMemoryAuthServerConfig extends AuthorizationServerConfigurerAdapter {
   

    @Resource
    UserDetailsService detailsService;
    @Resource
    PasswordEncoder encoder;
    @Resource
    AuthenticationManager manager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
   
        // 使用内存模式
        clients.inMemory()
                // 创建client为 a, secret 为 123, 这里框架要求secret必须强制加密,否则会报错
                .withClient("a").secret(encoder.encode("123"))
                // 权限,all所有, read, write
                .scopes("all")
                // 自动授权
                .autoApprove("all")
                // 转发跳转URL
                .redirectUris("https://www.baidu.com")
                // 认证类型 包含
                .authorizedGrantTypes(AuthorizationGrantType.AUTHORIZATION_CODE.getValue(), // authorization_code
                        AuthorizationGrantType.REFRESH_TOKEN.getValue(), // refresh_token
                        AuthorizationGrantType.IMPLICIT.getValue(), // implicit
                        AuthorizationGrantType.PASSWORD.getValue(), // password
                        AuthorizationGrantType.CLIENT_CREDENTIALS.getValue() // client_credentials
                );
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
   
        endpoints.userDetailsService(detailsService);// 开启刷新令牌必须指定, 它需要后台自动去认证
        endpoints.authenticationManager(manager); // 使用密码模式必须指定 manager,它需要后台自动去认证
    }
}

3. JdbcAuthServerConfig

@Configuration
public class JdbcAuthServerConfig extends AuthorizationServerConfigurerAdapter {
   


    @Resource
    PasswordEncoder encoder;
    @Resource
    AuthenticationManager manager;
    @Resource
    DataSource dataSource;

    @Bean // 配置token存储数据源
    public TokenStore tokenStore() {
   
        return new JdbcTokenStore(dataSource);
    }

    @Bean  // 声明ClientDetailsService,配置数据源 以及 加密方式
    public ClientDetailsService clientDetails(){
   
        JdbcClientDetailsService service = new JdbcClientDetailsService(dataSource);
        service.setPasswordEncoder(encoder);
        return service;
    }

    @Override // 配置 客户端 采用jdbc  模式,
    // 使用 jdbc() ,不能直接这么写 clients.jdbc(dataSource)  因为secret经过bcrypt加密,所以要指定加密方式
    // 所以要自定义 clientDetails
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
   
        clients.withClientDetails(clientDetails());
    }

    @Override // 配置认证端点
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
   
        endpoints.authenticationManager(manager);//认证管理器
        endpoints.tokenStore(tokenStore());//配置令牌存储为数据库存储

        // 配置TokenServices参数
        DefaultTokenServices tokenServices = new DefaultTokenServices();//修改默认令牌生成服务
        tokenServices.setTokenStore(endpoints.getTokenStore());//基于数据库令牌生成
        tokenServices.setSupportRefreshToken(true);//是否支持刷新令牌
        tokenServices.setReuseRefreshToken(true);//是否重复使用刷新令牌(直到过期)

        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());//设置客户端信息
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());//用来控制令牌存储增强策略
        //访问令牌的默认有效期(以秒为单位)。过期的令牌为零或负数。
        tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30天
        //刷新令牌的有效性(以秒为单位)。如果小于或等于零,则令牌将不会过期
        tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(3)); //3天
        endpoints.tokenServices(tokenServices);//使用配置令牌服务
    }
}
初始化脚本
SET NAMES utf8mb4;
SET
FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for clientdetails
-- ----------------------------
DROP TABLE IF EXISTS `clientdetails`;
CREATE TABLE `clientdetails`
(
    `appId`                  varchar(256) NOT NULL,
    `resourceIds`            varchar(256)  DEFAULT NULL,
    `appSecret`              varchar(256)  DEFAULT NULL,
    `scope`                  varchar(256)  DEFAULT NULL,
    `grantTypes`             varchar(256)  DEFAULT NULL,
    `redirectUrl`            varchar(256)  DEFAULT NULL,
    `authorities`            varchar(256)  DEFAULT NULL,
    `access_token_validity`  int(11) DEFAULT NULL,
    `refresh_token_validity` int(11) DEFAULT NULL,
    `additionalInformation`  varchar(4096) DEFAULT NULL,
    `autoApproveScopes`      varchar(256)  DEFAULT NULL,
    PRIMARY KEY (`appId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for oauth_access_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token`
(
    `token_id`          varchar(256) DEFAULT NULL,
    `token`             blob,
    `authentication_id` varchar(256) NOT NULL,
    `user_name`         varchar(256) DEFAULT NULL,
    `client_id`         varchar(256) DEFAULT NULL,
    `authentication`    blob,
    `refresh_token`     varchar(256) DEFAULT NULL,
    PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for oauth_approvals
-- ----------------------------
DROP TABLE IF EXISTS `oauth_approvals`;
CREATE TABLE `oauth_approvals`
(
    `userId`         varchar(256)       DEFAULT NULL,
    `clientId`       varchar(256)       DEFAULT NULL,
    `scope`          varchar(256)       DEFAULT NULL,
    `status`         varchar(10)        DEFAULT NULL,
    `expiresAt`      timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    `lastModifiedAt` date               DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`
(
    `client_id`               varchar(256) NOT NULL,
    `resource_ids`            varchar(256)  DEFAULT NULL,
    `client_secret`           varchar(256)  DEFAULT NULL,
    `scope`                   varchar(256)  DEFAULT NULL,
    `authorized_grant_types`  varchar(256)  DEFAULT NULL,
    `web_server_redirect_uri` varchar(256)  DEFAULT NULL,
    `authorities`             varchar(256)  DEFAULT NULL,
    `access_token_validity`   int(11) DEFAULT NULL,
    `refresh_token_validity`  int(11) DEFAULT NULL,
    `additional_information`  varchar(4096) DEFAULT NULL,
    `autoapprove`             varchar(256)  DEFAULT NULL,
    PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for oauth_client_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_token`;
CREATE TABLE `oauth_client_token`
(
    `token_id`          varchar(256) DEFAULT NULL,
    `token`             blob,
    `authentication_id` varchar(256) NOT NULL,
    `user_name`         varchar(256) DEFAULT NULL,
    `client_id`         varchar(256) DEFAULT NULL,
    PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for oauth_code
-- ----------------------------
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code`
(
    `code`           varchar(256) DEFAULT NULL,
    `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for oauth_refresh_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token`
(
    `token_id`       varchar(256) DEFAULT NULL,
    `token`          blob,
    `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

SET
FOREIGN_KEY_CHECKS = 1;

-- 写入客户端信息
-- client_id 为 a
-- client_secret 为 123
INSERT INTO `oauth_client_details`
VALUES ('a', NULL, '$2a$10$tbFi2xQwHQcjIp4jt5eGZ..CER7ws.Pzg9RiBM1tTGH1omkNprXqC', 'all',
        'authorization_code,refresh_token,password,client_credentials', 'https://www.baidu.com', NULL, NULL, NULL, NULL, NULL);

4. RedisAuthServerConfig

@Configuration
public class RedisAuthServerConfig extends AuthorizationServerConfigurerAdapter {
   

    @Resource
    private PasswordEncoder encoder;
    @Resource
    private AuthenticationManager manager;
    @Resource
    UserDetailsService detailsService;

    final RedisConnectionFactory factory;

    public RedisAuthServerConfig(RedisConnectionFactory factory) {
   
        this.factory = factory;
    }

    @Override //配置使用 redis 方式颁发令牌,同时配置 redis 转换器
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
   
        endpoints.tokenStore(tokenStore())  // 设置令牌生成方式
                .accessTokenConverter(converter())  // 设置令牌转换器
                .authenticationManager(manager)  // 使用 AuthenticationManager 管理
                .userDetailsService(detailsService); // 刷新令牌必须使用
    }

    @Bean //使用redis方式生成令牌
    public TokenStore tokenStore() {
   
        // 设置redis存储
        return new RedisTokenStore(factory);
    }

    @Bean// 使用默认的转换器
    public DefaultAccessTokenConverter converter() {
   
        return new DefaultAccessTokenConverter();
    }

    @Override//使用内存方式
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
   
        // 设置 clientDetails
        clients.inMemory().withClient("a").secret(encoder.encode("123"))
                .scopes("all")
                .autoApprove("all")
                .redirectUris("https://www.baidu.com")
                .authorizedGrantTypes("authorization_code", "refresh_token", "implicit", "password", "client_credentials");
    }
}

5. JwtAuthServerConfig

@Configuration
public class JwtAuthServerConfig extends AuthorizationServerConfigurerAdapter {
   

    @Resource
    private PasswordEncoder encoder;
    @Resource
    private AuthenticationManager manager;
    @Resource
    private DataSource dataSource;

    @Override //配置使用 jwt 方式颁发令牌,同时配置 jwt 转换器
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
   
        endpoints.tokenStore(tokenStore())  // 设置令牌生成方式
                .accessTokenConverter(converter())  // 设置jwt令牌转换器
                .authenticationManager(manager);  // 使用 AuthenticationManager 管理
    }

    @Bean //使用JWT方式生成令牌
    public TokenStore tokenStore() {
   
        // 设置jwt令牌转换器
        return new JwtTokenStore(converter());
    }

    @Bean//使用同一个密钥来编码 JWT 中的  OAuth2 令牌
    public JwtAccessTokenConverter converter() {
   
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");//可以采用属性注入方式 生产中建议使用复杂字符串,或者字符串加密,或者加盐
        return converter;
    }

    @Override//使用数据库方式客户端存储
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
   
        // 设置 clientDetails
        // 默认是 inMemory() ,基于内存
        // 如果使用 jdbc() ,不能直接这么写 clients.jdbc(dataSource)  因为secret经过bcrypt加密,所以要指定加密方式
        // 即需要声明 ClientDetails
        clients.withClientDetails(clientDetails());
    }

    @Bean // 声明 ClientDetails实现
    public ClientDetailsService clientDetails() {
   
        // 设置数据源
        JdbcClientDetailsService service = new JdbcClientDetailsService(dataSource);
        // 设置加密方式
        service.setPasswordEncoder(encoder);
        return service;
    }
}

资源服务器-核心代码

@Configuration
@EnableResourceServer // 开启资源服务器(也可以放在启动类上)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
   
    final DataSource dataSource;
    final RedisConnectionFactory factory;

    @Autowired
    public ResourceServerConfig(DataSource dataSource, RedisConnectionFactory factory) {
   
        this.dataSource = dataSource;
        this.factory = factory;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
   
        resources.tokenStore(tokenStore());
    }

    @Bean//使用同一个密钥来编码 JWT 中的  OAuth2 令牌
    public JwtAccessTokenConverter converter() {
   
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");//可以采用属性注入方式 生产中建议使用复杂字符串,或者字符串加密,或者加盐
        return converter;
    }

    @Bean
    public TokenStore tokenStore() {
   
        // 基于内存
        // 但是呢,如果授权服务器和资源服务器在一起,那么授权服务器需要登录,然后获得授权信息,
        // 此时访问同一服务下的资源,也是可以ok的,所以在这种情况下,这里就没必要了
        return new InMemoryTokenStore();
        // 基于JWT
//        return new JwtTokenStore(converter());
        // 基于数据库
//        return new JdbcTokenStore(dataSource);
        // 基于redis
//        return new RedisTokenStore(factory);
    }

    // 当然,也可以直接发http请求的方式校验
    //    ## OAuth2 标准接口
    //- /oauth/authorize:授权端点

    //- /oauth/token:获取令牌端点

    //- /oauth/confirm_access:用户确认授权提交端点

    //- /oauth/error:授权服务错误信息端点

    //- /oauth/check_token:用于资源服务访问的令牌解析端点

    //- /oauth/token_key:提供公有密匙的端点,如果使用JWT令牌的话
}

pom

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.2.5.RELEASE</spring-boot.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</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-oauth2</artifactId>
        </dependency>

        <!-- 引入资源服务器依赖 -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
        </dependency>

        <!--引入 jdbc 依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!--引入 redis 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
目录
相关文章
|
6月前
|
安全 Java Spring
Spring Security的授权&鉴权
该文档介绍了授权和鉴权的概念,主要分为Web授权和方法授权。Web授权通过URL拦截进行,而方法授权利用注解控制权限,粒度更细但耦合度高。在Web授权的案例中,展示了如何在Spring Security中对特定URL设置角色控制。此外,还列举了Spring Security内置的控制操作方法,如permitAll()、denyAll()和hasRole()等,用于定义不同类型的用户访问权限。
161 7
|
存储 安全 Java
Spring Security 认证的三种方式及简单的授权
Spring Security 认证的三种方式及简单的授权
115 0
|
存储 安全 Java
Spring Security Oauth2 之 密码模式请求/oauth/token 解析
前言 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/bin392328206/six-finger 种一棵树最好的时间是十年前,其次是现在
1496 0
|
存储 安全 前端开发
详解SpringSecurity认证(下)
详解SpringSecurity认证(下)
120 0
|
6月前
【SpringSecurity 】SpringSecurity 自定义登录页面
【SpringSecurity 】SpringSecurity 自定义登录页面
105 0
|
SQL 安全 Java
SpringBoot 整合SpringSecurity示例实现前后分离权限注解+JWT登录认证
SpringSecurity是一个用于Java 企业级应用程序的安全框架,主要包含用户认证和用户授权两个方面.相比较Shiro而言,Security功能更加的强大,它可以很容易地扩展以满足更多安全控制方面的需求,但也相对它的学习成本会更高,两种框架各有利弊.实际开发中还是要根据业务和项目的需求来决定使用哪一种.
SpringBoot 整合SpringSecurity示例实现前后分离权限注解+JWT登录认证
|
安全 前端开发 Java
详解SpringSecurity认证(上)
详解SpringSecurity认证(上)
163 0
|
JSON 安全 Java
SpringSecurity5.7+最新案例 -- 授权 --
书接上回 SpringSecurity5.7+最新案例 -- 用户名密码+验证码+记住我······ 本文 继续处理SpringSecurity授权 ...... 目前由 难 -&gt; 简,即自定义数据库授权,注解授权,config配置授权
140 0
|
JSON 前端开发 数据格式
SpringSecurity基础-认证授权结果处理
在传统的应用中,认证成功后页面需要跳转到认证成功页面或者跳转到个人中心页,但是在前后端分离的项目通常是使用Ajax请求完成认证,这时候我们需要返回一个JSON结果告知前端认证结果,然后前端自行跳转页面。 要做到上述功能,我们需要自定义认证成功处理器实现AuthenticationSuccessHandler接口复写 onAuthenticationSuccess方法,该方法其中一个参数是Authentication ,他里面封装了认证信息,用户信息UserDetails等,我们需要在这个方法中使用Response写出json数据即可
139 0
|
JSON 前端开发 数据格式
六.SpringSecurity基础-认证授权结果处理
SpringSecurity基础-认证授权结果处理