Oauth2.0 认证授权

1. 导入相关依赖

<!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.2.4.RELEASE</version>
        </dependency>

2. 搭建授权服务器

  • 继承AuthorizationServerConfigurerAdapter重写授权服务器端点相关配置
package com.intretech.open.config;

import com.intretech.open.service.OauthUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

/**
 * 授权服务器配置
 *
 * <p>授权服务器配置主要做令牌端点的安全约束、令牌访问端点配置、管理令牌、配置客户端详细信息.
 *
 * @author Andrew
 * @date 2021/10/6
 */
@Configuration
@EnableAuthorizationServer
public class CodeAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 数据库连接池对象
     */
    @Autowired
    private DataSource dataSource;

    /**
     * 认证业务对象
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 用户信息
     */
    @Autowired
    private OauthUserDetailsService userDetailsService;

    /**
     * 从数据库中查询出客户端信息
     *
     * @return {@link JdbcClientDetailsService}
     */
    @Bean
    public JdbcClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * token保存策略
     *
     * @return {@link TokenStore}
     */
    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    /**
     * 授权信息保存策略
     *
     * @return {@link ApprovalStore}
     */
    @Bean
    public ApprovalStore approvalStore() {
        return new JdbcApprovalStore(dataSource);
    }

    /**
     * 授权码模式专用对象
     *
     * @return {@link AuthorizationCodeServices}
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    /**
     * 指定客户端登录信息来源
     *
     * @param clients {@link ClientDetailsServiceConfigurer}
     * @throws Exception Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());
    }


    /**
     * 检查token的策略
     *
     * @param oauthServer {@link AuthorizationServerSecurityConfigurer}
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
        // 允许表单认证
        oauthServer.allowFormAuthenticationForClients()
                // 开启 /oauth/check_token 资源服务器请求端点来检查令牌是否有效
                .checkTokenAccess("isAuthenticated()");
    }


    /**
     * OAuth2的配置信息
     *
     * @param endpoints {@link AuthorizationServerEndpointsConfigurer}
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                // 授权策略
                .approvalStore(approvalStore())
                // 认证策略
                .authenticationManager(authenticationManager)
                // 授权码生成消费策略
                .authorizationCodeServices(authorizationCodeServices())
                // token 保存策略
                .tokenStore(tokenStore())
                // 用户信息存储策略,不注入将无法使用 refresh_token
                .userDetailsService(userDetailsService);
    }
}
  • 继承WebSecurityConfigurerAdapter对用户登录的相关端点进行配置
package com.intretech.open.config;

import com.intretech.open.service.OauthUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * 安全配置类
 *
 * <p>默认比 ResourceServerConfigurerAdapter 执行顺序低,保护 oauth 相关的 endpoints,同时主要作用于用户的登录.
 *
 * @author Andrew
 * @date 2021/10/6
 */
@Order(2)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class CodeWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private OauthUserDetailsService userService;


    /**
     * 访问权限设置
     *
     * @param http {@link HttpSecurity}
     * @throws Exception Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 以下请求无须认证
                .antMatchers("/get/**").permitAll()
                // 所有请求均需登录认证
                .anyRequest().authenticated()
                .and()
                // 允许表单提交
                .formLogin()
                // 登录成功后跳转URL,即提交表单信息到此
                .loginProcessingUrl("/login")
                .permitAll()
                .and()
                // 关闭跨站请求伪造保护
                .csrf()
                .disable();
    }

    /**
     * 密码加密方式
     *
     * @return {@link PasswordEncoder}
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 指定认证对象的来源
     *
     * @param auth {@link AuthenticationManagerBuilder}
     * @throws Exception Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }

    /**
     * AuthenticationManager对象在Oauth2认证服务中要使用,提取放到IOC容器中
     *
     * @return {@link AuthenticationManager}
     * @throws Exception Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

3. 搭建资源服务器

继承ResourceServerConfigurerAdapter对资源服务器开放资源进行权限控制

package com.intretech.open.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

/**
 * 资源服务器配置
 *
 * <p>默认比 ResourceServerConfigurerAdapter 执行顺序高,用于保护oauth要开放的资源,主要作用于client端以及token的认证,配置需要 access_token 访问的url.
 *
 * @author Andrew
 * @date 2021/10/6
 */
@Order(3)
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true)
public class CodeResourceServerConfig extends ResourceServerConfigurerAdapter {

    private static final String RESOURCE_ID = "ning_de_elementary_school_open_server";

    @Autowired
    private DataSource dataSource;

    /**
     * 指定token的持久化策略
     * InMemoryTokenStore表示将token存储在内存中
     * Redis表示将token存储在redis中
     * JdbcTokenStore表示将token存储在数据库中
     *
     * @return {@link TokenStore}
     */
    @Bean
    public TokenStore jdbcTokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    /**
     * 指定当前的资源 ID 和存储方案
     *
     * @param resources {@link ResourceServerSecurityConfigurer}
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(RESOURCE_ID).tokenStore(jdbcTokenStore());
    }

    /**
     * 配置访问权限
     *
     * @param http {@link HttpSecurity}
     * @throws Exception Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 指定不同请求方式访问资源所需要的权限,一般查询是read,其余是write。
                .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
                .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')")
                .and()
                .headers().addHeaderWriter((request, response) -> {
            // 允许跨域
            response.addHeader("Access-Control-Allow-Origin", "*");
            // 如果是跨域的预检请求,则原封不动向下传达请求头信息
            if ("OPTIONS".equals(request.getMethod())) {
                response.setHeader("Access-Control-Allow-Methods", request.getHeader("AccessControl-Request-Method"));
                response.setHeader("Access-Control-Allow-Headers", request.getHeader("AccessControl-Request-Headers"));
            }
        });
    }
}

4. 导入相关数据表

/*
 Navicat MySQL Data Transfer

 Source Server         : native
 Source Server Type    : MySQL
 Source Server Version : 50732
 Source Host           : localhost:3306
 Source Schema         : security

 Target Server Type    : MySQL
 Target Server Version : 50732
 File Encoding         : 65001

 Date: 11/10/2021 16:23:27
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for oauth_access_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token`  (
  `token_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `token` longblob NULL,
  `authentication_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authentication` longblob NULL,
  `refresh_token` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Table structure for oauth_approvals
-- ----------------------------
DROP TABLE IF EXISTS `oauth_approvals`;
CREATE TABLE `oauth_approvals`  (
  `userId` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `clientId` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `expiresAt` datetime NULL DEFAULT NULL,
  `lastModifiedAt` datetime NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Table structure for oauth_client_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_token`;
CREATE TABLE `oauth_client_token`  (
  `token_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `token` longblob NULL,
  `authentication_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Table structure for oauth_code
-- ----------------------------
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code`  (
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authentication` varbinary(2550) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Table structure for oauth_refresh_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token`  (
  `token_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `token` longblob NULL,
  `authentication` longblob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Table structure for oauth_role
-- ----------------------------
DROP TABLE IF EXISTS `oauth_role`;
CREATE TABLE `oauth_role`  (
  `id` int(11) NOT NULL COMMENT '编号',
  `role_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
  `role_desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Table structure for oauth_user
-- ----------------------------
DROP TABLE IF EXISTS `oauth_user`;
CREATE TABLE `oauth_user`  (
  `id` int(11) NOT NULL,
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名称',
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
  `status` int(255) NULL DEFAULT NULL COMMENT '1开启0关闭',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Table structure for oauth_user_role
-- ----------------------------
DROP TABLE IF EXISTS `oauth_user_role`;
CREATE TABLE `oauth_user_role`  (
  `UID` int(11) NOT NULL COMMENT '用户编号',
  `RID` int(11) NOT NULL COMMENT '角色编号',
  PRIMARY KEY (`UID`, `RID`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

SET FOREIGN_KEY_CHECKS = 1;

5. Security 相关知识介绍

  • 常用访问接口
/oauth/authorize:申请授权码code
/oauth/token:获取令牌token
/oauth/check_token:用于资源服务器请求端点来检查令牌是否有效
/oauth/confirm_access:用户确认授权提交
/oauth/error:授权服务错误信息
/oauth/token_key:提供公有密钥的端点,使用JWT令牌时会使用
  • 数据表字段解释

官方指定表:https://andaily.com/spring-oauth-server/db_table_description.html

自定义用户表:https://blog.csdn.net/weixin_45457922/article/details/113119571 (博客里有介绍)

  • 授权码模式访问流程

授权四模式访问流程请参考此博客:https://blog.csdn.net/lizhiqiang1217/article/details/90523727

  • 参数解释
参数名 含义 类型 是否必须 描述
grant_type 授权类型 String 固定值:client_credentials
client_id 客户端ID String 固定值,由服务提供方生成授予
client_secret 客户端密码 String 固定值,由服务提供方生成授予
scope 权限范围 Integer 客户端可访问的资源范围
access_token 权限码 String 客户端访问资源服务器权限码
token_type token 类型 String 生成和呈现access_token的方式
expires_in token过期时间 Integer token失效时间
scope 权限范围 String 客户端可访问的资源范围
refresh_token 刷新token String 授权码/密码模式才有

6. 细节点

  • 若认证服务器与资源服务器为同一个,需要注意 WebSecurityConfigurerAdapter 与 ResourceServerConfigurerAdapter 加载顺序,避免出现请求被资源服务器拦截。
  • 配置无须登录认证请求,避免请求被拦截。
  • 可以支持多种模式同时存在。
  • 授权码模式请求授权码客户端与请求 access_token 客户端要保持一致,否则无法请求 access_token。
posted @ 2022-03-20 16:08  生活是很好玩的  阅读(105)  评论(0编辑  收藏  举报