SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用

老老老JR老北
发布于 2022-7-25 17:30
浏览
0收藏

作者 | 宇木木兮
来源 |今日头条

学习目标
统一认证与授权——Oauth2.0开放授权平台
第1章 Oauth2.0简介
OAuth2.0是OAuth协议的延续版本,但不向前兼容OAuth 1.0(即完全废止了OAuth1.0)。 OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。

上面这段话是百度百科上面给的解释,其实Oauth2.0说白了就是一种协议,而按照这种协议的话,我们能实现第三方授权操作;第三方授权其实我们也都用到过,就比如说现在我要去爱奇艺看电视,而有些资源是需要VIP的,那我首先得登录吧,然后登录之后再充个会员就可以看了。那登录的时候你会发现,可以选择微信等账号登录,这其实就是Oauth2.0的应用。

1.1 Oauth2.0中的角色
在正式讲解Oauth2.0的实现流程与实战之前,我们先来明确一下这个玩意中的几种角色。网上也有一大堆资料,但是可能很多同学可能分不太清这些角色到底是什么,OK,那我这里给大家做个解释。

还是按照上面那个登录爱奇艺的例子来看,我们要通过微信这种方式去登录爱奇艺,那这个时候实际上就是我们在点击微信登录按钮的时候,首先第三方客户端(也就是爱奇艺)要先重定向请求到微信的某个接口,请求认证,如果通过认证了,微信就会返回一个token给爱奇艺,然后爱奇艺再根据这个token去微信的另一个接口获取我的微信名啊、头像啊等等资源。这就是大体流程,后面会给出具体流程图。接下来我们根据这个流程来分析一下有哪些角色。

  • 资源所有者(Resource Owner):就是我,资源只的就是我在微信上面的头像啊,昵称啊等资源。
  • 第三方应用:或者称为第三方客户端(Clinet),希望使用资源服务器提供的资源,这里就是爱奇艺,它想要使用我的微信名,微信头像等资源。
  • 认证服务器(Authorization Server):专门用于对资源所有者的身份进行认证,对要访问的资源进行授权、产生令牌的服务器。访问资源,需要通过认证服务器由资源所有者授权才可以访问。这里其实就是微信的授权系统。
  • 资源服务器(Resource Server):存储用户的资源,验证令牌有效性。这里指的就是微信中保存我微信信息的服务器。
  • 服务提供商(Service Provider):认证服务和资源服务归属于一个机构,该机构就是服务提供商。就是腾讯嘛。
    OK,明确了角色的概念之后,再来看看这个登录的具体流程。

1.2 Oauth2.0模式
OAuth2.0在第三方应用和服务提供商之间,设置一个授权层(authorization layer)。这样做的目的就是因为第三方应用它不能直接登录服务提供商的,就像爱奇艺不能直接登录微信一样,那这个时候如果爱奇艺想要用微信的资源的话,怎么办?就是在他们之间增加一个授权层,让用户给爱奇艺授权,这样,爱奇艺就能使用该用户的某一部分资源了。而Oauth2.0又分为四种授权方式,接下来每种模式都介绍一下。

1.2.1 授权码模式

Authorization Code

授权码模式(Authorization Code):功能是最完整的,流程也是最严密的,国内各大服务提供商(微信、微博、淘宝、百度)都是使用此授权模式进行授权。该授权模式可以确定是用户进行授权的,并且令牌是认证服务器发放到第三方应用服务器上,而不是浏览器上。该模式的认证流程如下:

  1. 用户进入爱奇艺应用,想看特殊电影,那得登录啊。
  2. 点击微信登录,就会跳转到微信的登录页面,这是爱奇艺重定向到微信的认证服务端
  3. 返回一个授权页面给用户
  4. 用户微信扫码或者直接输入账号密码进行授权
  5. 微信认证服务器产生一个授权码返回给爱奇艺
  6. 爱奇艺拿到这个授权码然后再发送请求到微信认证服务器请求一个token
  7. 微信认证服务器认证成功返回给爱奇艺一个token
  8. 爱奇艺拿着这个token请求微信的资源服务器获取微信名和头像
  9. 微信资源服务器校验token成功后响应这些信息给爱奇艺
  10. 爱奇艺登录成功
  11. 返回特殊电影给用户

SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用-鸿蒙开发者社区

1.2.2 简化模式
简化模式(Implicit):和授权码模式不同的是,令牌发放给浏览器,OAuth2客户端运行在浏览器中。而不是发放该第三方应用的服务器。SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用-鸿蒙开发者社区1.2.3 密码模式
密码模式(resource owner password credentials):将用户和密码传过去,直接获取accesstokne,用户同意授权动作是在第三方应用上完成,而不是在认证服务器。第三方应用申请令牌时,直接带用户名和密码去向认证服务器申请令牌。这种方式认证服务器无法断定用户是否真的授权,用户和密码可能是第三方应用盗取过来的。

流程如下:

  1. 用户向客户端直接提供认证服务器想要的用户名和密码。
  2. 客户端将用户名和密码发给认证服务器,向认证服务器请求令牌
  3. 认证服务器确认后,向客户端提供访问令牌
  4. 后面的流程跟上面的类似

SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用-鸿蒙开发者社区1.2.4 客户端模式
客户端模式(client credentials):这种模式实际上不是让用户授权去登录微信了,而是让客户端也就是爱奇艺自己去登录微信,拿相应的资源。这种模式使用较少,当一个第三方应用自己本身需要获取资源,而不是获取用户资源时,客户端模式十分有用。

流程如下:

  1. 客户端向认证服务器进行身份认证,并要求一个访问令牌
  2. 认证服务器确认后,向客户端提供访问令牌

SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用-鸿蒙开发者社区第2章 授权码模式实战
在明白了Oauth2.0到底是个什么玩意儿,以及它的四种模式之后,接下来我们通过SpringSecurity来实现一下授权码模式,这种模式是市面上用的最广的。项目框架依然是以mall这个项目为主。但是会创建一个单独的认证服务器模块(mall-oauth2)和资源服务器模块(mall-resource)。

2.1 认证服务器
认证服务器是服务提供者专门用来处理认证授权的服务器,主要负责获取用户授权并颁发token,以及完成后续的token认证工作。认证部分功能主要由spring security 负责,授权则由oauth2负责。

2.1.1 项目搭建
创建一个springboot项目 mall-oauth2项目,配置pom

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>mall</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>mall-oauth2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mall-oauth2</name>
    <description>认证服务器</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
    </dependencies>
</project>

2.1.2 表结构设计
1.在oauth2库中增加表结构

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` blob 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` blob 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;
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` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
  `autoapprove` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'false',
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code`  (
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
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` blob NULL,
  `authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO oauth_client_details(client_id,resource_ids,client_secret,scope,authorized_grant_types,web_server_redirect_uri,autoapprove) VALUES('iqiyi','resource1','$2a$10$m7rm4duMVCXbRAC0YChzsu8irZmAngLRHwJpBzPZjtT72yDxRYHVC','all','authorization_code','https://www.iqiyi.com','true');
  • oauth_access_token:存储生成的access_token,由类JdbcTokenStore操作
  • oauth_client_details:存储客户端的配置信息,由类JdbcClientDetailsService操作
  • oauth_code:存储服务端系统生成的code的值,由类JdbcAuthorizationCodeServices操作
  • oauth_refresh_token:存储刷新令牌的refresh_token,如果客户端的grant_type不支持refresh_token,那么不会用到这张表,同样由类JdbcTokenStore操作
    2.然后创建用户和角色表
-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `parent_id` bigint(11) NULL DEFAULT NULL COMMENT '用户ID',
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '权限名字',
  `ename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '权限名字',
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '请求路径',
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '描述',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1, 0, '系统管理', 'System', '/', NULL, '2019-05-30 16:22:20', '2019-05-30 16:22:24');
INSERT INTO `permission` VALUES (2, 0, '用户管理', 'SystemUser', '/users', NULL, '2019-05-30 16:23:28', '2019-05-30 16:23:32');
INSERT INTO `permission` VALUES (3, 0, '查看用户', 'SystemUserView', NULL, NULL, '2019-05-30 16:24:29', '2019-05-30 16:24:33');
INSERT INTO `permission` VALUES (4, 0, '新增用户', 'SystemUserInsert', NULL, NULL, '2019-05-30 16:25:09', '2019-05-30 16:25:13');
INSERT INTO `permission` VALUES (5, 0, '编辑用户', 'SystemUserUpdate', NULL, NULL, '2019-05-30 16:25:53', '2019-05-30 16:25:57');
INSERT INTO `permission` VALUES (6, 0, '删除用户', 'SystemUserDelete', NULL, NULL, '2019-05-30 16:26:49', '2019-05-30 16:26:54');
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `parent_id` bigint(20) NULL DEFAULT NULL COMMENT '父类ID',
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '角色名字',
  `ename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '角色名字',
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '描述',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 0, '超级管理员', 'ADMIN', NULL, '2019-05-30 16:09:53', '2019-05-30 16:09:57');
-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission`  (
  `id` bigint(11) NOT NULL COMMENT '主键ID',
  `role_id` bigint(11) NULL DEFAULT NULL COMMENT '角色ID',
  `permission_id` bigint(11) NULL DEFAULT NULL COMMENT '权限ID',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES (1, 1, 1);
INSERT INTO `role_permission` VALUES (2, 1, 2);
INSERT INTO `role_permission` VALUES (3, 1, 3);
INSERT INTO `role_permission` VALUES (4, 1, 4);
INSERT INTO `role_permission` VALUES (5, 1, 5);
INSERT INTO `role_permission` VALUES (6, 1, 6);
-- ----------------------------
-- Table structure for my_user
-- ----------------------------
DROP TABLE IF EXISTS `my_user`;
CREATE TABLE `my_user`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '密码',
  `gender` int(2) NULL DEFAULT NULL COMMENT '性别(1男 2女)',
  `email` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '邮箱',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '用户创建时间',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `removed` int(2) NULL DEFAULT NULL COMMENT '是否删除(1删除0未删除)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of my_user
-- ----------------------------
INSERT INTO `my_user` VALUES (1, 'admin', '$2a$10$BDRAkgioU9jqN8eNVrRcluQd9GcocNv3EB8VfdKYfW8iR3vSbY3pW', 1, NULL, '2019-05-30 15:53:45', '2019-05-30 15:53:51', 0);
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `id` bigint(11) NOT NULL COMMENT '主键ID',
  `user_id` bigint(11) NULL DEFAULT NULL COMMENT '用户主键',
  `role_id` bigint(11) NULL DEFAULT NULL COMMENT '角色ID',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);

SET FOREIGN_KEY_CHECKS = 1;

2.1.3 配置
1.配置文件

# 应用名称
server.port=7070
spring.application.name=mall-oauth2
spring.thymeleaf.cache=false
spring.datasource.druid.url=jdbc:mysql://192.168.8.74:3306/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.initial-size=30
spring.datasource.druid.max-active=100
spring.datasource.druid.min-idle=10
spring.datasource.druid.max-wait=60000
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.filters=stat,wall
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.configuration.auto-mapping-behavior=full
mybatis-plus.mapper-locations=classpath*:mapper/*Mapper.xml

2.开启Spring Security 安全配置

创建WebSecurityConfig 继承WebSecurityConfigurerAdapter

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
}

通过@EnableWebSecurity 开启Spring Security配置,继承WebSecurityConfigurerAdapter的方法,实现个性化配置。如果使用内存配置用户,可以重写其中的configure方法进行配置,由于我们使用数据库中的用户信息,所以不需要在这里进行配置。并且采用认证服务器和资源服务器分离,也不需要在这里对服务资源进行权限的配置。

在类中创建了两个Bean,分别是用于处理认证请求的认证管理器AuthenticationManager,以及配置全局统一使用的密码加密方式BCryptPasswordEncoder,它们会在认证服务中被使用。

3.创建认证服务配置类

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;    //认证管理器
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;//密码加密方式
    @Autowired
    private DataSource dataSource;  // 注入数据源
    @Autowired
    private UserDetailsService userDetailsService; //自定义用户身份认证
    @Bean
    public ClientDetailsService jdbcClientDetailsService(){
        //将client信息存储在数据库中
        return new JdbcClientDetailsService(dataSource);
    }
    @Bean
    public TokenStore tokenStore(){
        //对token进行持久化存储在数据库中,数据存储在oauth_access_token和oauth_refresh_token
        return new JdbcTokenStore(dataSource);
    }
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        //加入对授权码模式的支持
        return new JdbcAuthorizationCodeServices(dataSource);
    }
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //设置客户端的配置从数据库中读取,存储在oauth_client_details表
        clients.withClientDetails(jdbcClientDetailsService());
    }
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .tokenStore(tokenStore())//token存储方式
                .authenticationManager(authenticationManager)// 开启密码验证,来源于 WebSecurityConfigurerAdapter
                .userDetailsService(userDetailsService)// 读取验证用户的信息
                .authorizationCodeServices(authorizationCodeServices())
                .setClientDetailsService(jdbcClientDetailsService());
    }
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //  配置Endpoint,允许请求,不被Spring-security拦截
        security.tokenKeyAccess("permitAll()") // 开启/oauth/token_key 验证端口无权限访问
                .checkTokenAccess("isAuthenticated()") // 开启/oauth/check_token 验证端口认证权限访问
                .allowFormAuthenticationForClients()// 允许表单认证
                .passwordEncoder(passwordEncoder);   // 配置BCrypt加密
    }

}

在类中,通过@EnableAuthorizationServer 注解开启认证服务,通过继承父类AuthorizationServerConfigurerAdapter,对以下信息进行了配置:

ClientDetailsServiceConfigurer:配置客户端服务,这里我们通过JdbcClientDetailsService从数据库读取相应的客户端配置信息,进入源码可以看到客户端信息是从表oauth_client_details中拉取。

AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点,以及令牌服务的配置信息。该类作为一个装载类,装载了Endpoints所有的相关配置。

AuthorizationServerSecurityConfigurer:配置令牌端点(endpoint)的安全约束,OAuth2开放了端点用于检查令牌,/oauth/check_token和/oauth/token_key这些端点默认受到保护,在这里配置可被外部调用。

4.采用从数据库中获取用户信息的方式进行身份验证

@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    private UserService userService;
    @Autowired
    private PermissionService permissionService;
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        MyUser myUser = userService.getUserByUserName(userName);
        if (myUser==null){
            throw new UsernameNotFoundException("username : "+userName+" is not exist");
        }
        List<GrantedAuthority> authorities=new ArrayList<>();
        //获取用户权限
        List<Permission> permissions = permissionService.getByUserId(myUser.getId());
        permissions.forEach(permission->{
            authorities.add(new SimpleGrantedAuthority(permission.getEname()));
        });
        return new User(myUser.getUsername(),myUser.getPassword(),authorities);
    }

}

创建UserDetailServiceImpl 实现UserDetailsService接口,并实现loadUserByUsername方法,根据用户名从数据库查询用户信息及权限。

5.创建MyUser和Permission等实体类,具体参考代码

2.1.4 启动服务
首先发起请求获取授权码(code),直接访问下面的url:

http://localhost:7070/oauth/authorize?client_id=iqiyi&response_type=code

  • client_id:因为认证服务器要知道是哪一个应用在请求授权,所以client_id就是认证服务器给每个应用分配的id
  • redirect_uri:重定向地址,会在这个重定向地址后面附加授权码,让第三方应用获取code
  • response_type:code表明采用授权码认证模式
  • scope:需要获得哪些授权,这个参数的值是由服务提供商定义的,不能随意填写
    首先会重定向到登录验证页面,因为之前的url中只明确了第三方应用的身份,这里要确定第三方应用要请求哪一个用户的授权。输入数据库表user中配置的用户信息 admin/123456:

SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用-鸿蒙开发者社区注意url中请求的参数必须和在数据库中的表oauth_client中配置的相同,如果不存在或信息不一致都会报错,在参数填写错误时会产生如下报错信息:SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用-鸿蒙开发者社区如果参数完全匹配,会请求用户向请求资源的客户端client授权,因为我数据库的autoapprove字段设置为true了,所以直接会进行授权。并且页面会跳转到redirect_uri定义的地址,并在uri后面带上code授权码。SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用-鸿蒙开发者社区这样,用户的登录和授权的操作都在浏览器中完成了,接下来我们需要获取令牌,发送post请求到/oauth/token接口,使用授权码获取access_token。在发送请求时,需要在请求头中包含clientId和clientSecret,并且携带参数 grant_type、code、redirect_uri,这里会对redirect_uri做二次验证:SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用-鸿蒙开发者社区这样,就通过/oauth/token端点获取到了access_token,并一同拿到了它的令牌类型、过期时间、授权范围信息,这个令牌将在请求资源服务器的资源时被使用。

2.1.5 相关参数说明
配置说明:

  • 可以配置:"authorization_code", "password", "implicit","client_credentials","refresh_token"
  • scopes:授权范围标识,比如指定微服务名称,则只可以访问指定的微服务
  • autoApprove: false跳转到授权页面手动点击授权,true不需要手动授权,直接响应授权码
  • redirectUris:当获取授权码后,认证服务器会重定向到指定的这个URL,并且带着一个授权码code响应。
  • withClient:允许访问此认证服务器的客户端ID
  • secret:客户端密码,加密存储
  • authorizedGrantTypes:授权类型,支持同时多种授权类型
    令牌访问端点 Spring Security对OAuth2提供了默认可访问端点,即URL
  • /oauth/authorize:申请授权码code,涉及类AuthorizationEndpoint
  • /oauth/token:获取令牌token,涉及类TokenEndpoint
  • /oauth/check_token:用于资源服务器请求端点来检查令牌是否有效,涉及类CheckTokenEndpoint
  • /oauth/confirm_access:用于确认授权提交,涉及类WhitelabelApprovalEndpoint
  • /oauth/error:授权错误信息,涉及WhitelabelErrorEndpoint
  • /oauth/token_key:提供公有密匙的端点,使用JWT令牌时会使用,涉及类TokenKeyEndpoint
    2.2 资源服务器
    上文介绍了认证服务器的搭建,也通过例子从认证服务器中获取到了token值,接下来,我们就搭建一下资源服务器,当爱奇艺发送token过来之后,资源服务器通过验证返回资源。资源服务器其实就理解成一个springboot的web工程就完事了,可以提供一些controller接口,它和认证服务器可以部署在一起,也可以分离部署,我们这里采用分开部署的形式。

2.2.1 项目搭建
创建一个springboot的web项目 mall-resource项目,配置pom

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>mall</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>mall-resource</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mall-resource</name>
    <description>资源服务器</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <dependencies>
        <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>

    </dependencies>
</project>

2.2.2 配置
1.增加配置类

@Configuration
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    @Primary
    public RemoteTokenServices remoteTokenServices(){
        final RemoteTokenServices tokenServices=new RemoteTokenServices();
        //设置授权服务器check_token Endpoint 完整地址
        tokenServices.setCheckTokenEndpointUrl("http://localhost:7070/oauth/check_token");
        //设置客户端id与secret,注意:client_secret 值不能使用passwordEncoder加密
        tokenServices.setClientId("iqiyi");
        tokenServices.setClientSecret("iqiyimima");
        return tokenServices;
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        http.authorizeRequests()
                .anyRequest().authenticated();
    }
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("oauth2").stateless(true);
    }
}

在类中主要实现了以下功能:

  • @EnableResourceServer注解表明开启OAuth2资源服务器,在请求资源服务器的请求前,需要通过认证服务器获取access_token令牌,然后在访问资源服务器中的资源时需要携带令牌才能正常进行请求
  • 通过RemoteTokenServices实现自定义认证服务器,这里配置了我们之前创建的认证服务器
  • 重写configure(HttpSecurity http)方法,开启所有请求需要授权才可以访问
  • 配置资源相关设置configure(ResourceServerSecurityConfigurer resources),这里只设置resourceId,作为该服务资源的唯一标识
  • @EnableGlobalMethodSecurity(prePostEnabled = true):开启方法级权限控制
  • 创建 RemoteTokenServices 远程校验令牌服务,去校验令牌有效性,因为当前认证和资源服务器不是在同一工程中,所以要通过远程调用认证服务器校验令牌是否有效,如果认证和资源服务器在同一工程中,可以使用 DefaultTokenServices 配置校验令牌。
    2.controller
@RestController
public class UserController {
    @GetMapping("/getUser")
    public MyUser user (@RequestParam(value = "name") String name){
        return new MyUser(name,"很帅气的头像","女");
    }
}
# 应用名称
spring.application.name=mall-resource
# 应用服务 WEB 访问端口
server.port=7071

2.2.3 测试
1.不带access_token访问SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用-鸿蒙开发者社区2.带上access_token之后

使用Postman,在Authorization中配置使用Bearer Token,并填入从认证服务器获取的access_token(或在Headers中的Authorization字段直接填写Bearer 'access_token'),再次访问接口,可以正常访问接口资源:

SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用-鸿蒙开发者社区

分类
已于2022-7-25 17:30:44修改
收藏
回复
举报
回复
    相关推荐