SpringCloud整合 Oauth2+Gateway+Jwt+Nacos 实现授权码模式的服务认证(一)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: SpringCloud整合 Oauth2+Gateway+Jwt+Nacos 实现授权码模式的服务认证(一)

微信截图_20230209173852.png

前言

目前正在出一个SpringCloud进阶系列教程,含源码解读, 篇幅会较多, 喜欢的话,给个关注❤️ ~


前段时间拖更了,主要事情比较多和杂,不多废话了,直接给大家开整吧~ 本节重点是给大家介绍Oauth2,将会带大家从0到1搭建一个 SpringCloud项目,以及整合Oauth2 Server,实现基本的授权和认证功能。


什么是OAuth2.0

跟往常一样,在学习新知识前,首先明白它是啥?


我相信大部分人应该都对它都有了解,甚至使用过。如果你没有亲自搭建过这样的服务,但是在对接第三方平台的时候,首先需要去进行服务认证,在大多数的认证服务中,很多都是基于OAuth2.0的。


首先我们需要明白这样一个概念, OAuth ,它是一个授权(Authorization)的开放标准,大家都可以基于这个标准去实现。OAuth基于HTTPS,以及APIs,Service应用使用```access_token````来进行身份验证。


它主要有OAuth 1.0OAuth 2.0,二者不兼容。OAuth2.0 是目前广泛使用的版本,大部分说的OAuth都是OAuth2.0


基本流程

它的基本流程是怎么样的呢?我们先看张图

微信截图_20230209175138.png


上图中所涉及到的对象分别为:

  • Client 第三方应用,我们的应用就是一个Client
  • Resource Owner 资源所有者,即用户
  • Authorization Server 授权服务器,即提供第三方登录服务的服务器,如Github
  • Resource Server 拥有资源信息的服务器,通常和授权服务器属于同一应用


根据上图的信息,我们可以知道OAuth2的基本流程为:

  1. 第三方应用请求用户授权。
  2. 用户同意授权,并返回一个凭证(code)
  3. 第三方应用通过第二步的凭证(code)向授权服务器请求授权
  4. 授权服务器验证凭证(code)通过后,同意授权,并返回一个资源访问的凭证(Access Token)
  5. 第三方应用通过第四步的凭证(Access Token)向资源服务器请求相关资源。
  6. 资源服务器验证凭证(Access Token)通过后,将第三方应用请求的资源返回。


基本概念就介绍到这里,下面我们就一起整一下吧~


基本项目框架搭建

首先我们的准备工作,需要安装好Nacos,如果还不会的同学可以参考我之前写的文章,这里默认大家都安装好了。本节呢,我们统一给大家尝试高版本,我这里的SpringBoot版本是2.6+,SpringCloud2021,基本上都是比较新的版本了

下面新建一个maven项目,父工程的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>pkq-cloud</groupId>
    <artifactId>com.pkq.cloud</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>spring-cloud-oauth2-server</module>
    </modules>
    <packaging>pom</packaging>
    <properties>
        <fate.project.version>1.0.0</fate.project.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <maven.plugin.version>3.8.1</maven.plugin.version>
        <spring.boot.version>2.6.3</spring.boot.version>
        <spring-cloud.version>2021.0.1</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
        <alibaba.nacos.version>1.4.2</alibaba.nacos.version>
        <alibaba.sentinel.version>1.8.3</alibaba.sentinel.version>
        <alibaba.dubbo.version>2.7.15</alibaba.dubbo.version>
        <alibaba.rocketMq.version>4.9.2</alibaba.rocketMq.version>
        <alibaba.seata.version>1.4.2</alibaba.seata.version>
        <mybatis.plus.version>3.5.1</mybatis.plus.version>
        <knife4j.version>3.0.2</knife4j.version>
        <swagger.version>3.0.0</swagger.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <!-- springBoot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- springCloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>
复制代码


大家可以直接复制过去,下面我们新建一个模块叫spring-cloud-oauth2-server,这个模块就是OAuth2的授权服务中心,看下它的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">
    <parent>
        <artifactId>com.pkq.cloud</artifactId>
        <groupId>pkq-cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>spring-cloud-oauth2-server</artifactId>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--SpringBoot2.4.x之后默认不加载bootstrap.yml文件,需要在pom里加上依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <!-- Nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.alibaba.nacos</groupId>
                    <artifactId>nacos-client</artifactId>
                </exclusion>
            </exclusions>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.alibaba.nacos</groupId>
                    <artifactId>nacos-client</artifactId>
                </exclusion>
            </exclusions>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>${alibaba.nacos.version}</version>
        </dependency>
        <!-- Druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.22</version>
        </dependency>
        <!-- MySQL -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <!-- MyBatis-Plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis.plus.version}</version>
        </dependency>
        <!-- redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>9.9.3</version>
        </dependency>
        <!--security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.0</version>
        </dependency>
        <!--thymeleaf-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>
</project>
复制代码


该模块下的配置文件bootstrap.yml,不知道啥是bootstrap配置文件的可以参考我之前的文章,在讲Nacos的时候有给大家讲

server:
  port: 10001
spring:
  application:
    name: oauth2-server
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: ********
    driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
    host: 127.0.0.1
    port: 6379
    database: 2
  cloud:
    nacos:
      discovery:
        #注册中心地址
        server-addr: 127.0.0.1:8848
      config:
        file-extension: yaml
        #配置中心地址
        server-addr: 127.0.0.1:8848
  thymeleaf:
    cache: false
mybatis-plus:
  mapper-locations: classpath:mapper/*/*.xml,mapper/*.xml
  global-config:
    db-config:
      id-type: auto
      field-strategy: NOT_EMPTY
      db-type: MYSQL
  configuration:
    map-underscore-to-camel-case: true
    call-setters-on-nulls: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
复制代码


这里为了方便演示,直接把配置写到文件了,也可以放到Nacos配置中心里,这里主要是当服务注册中心使用, Nacos用到的sql我放到项目的db目录下了,需要的可自行获取

下面我们加个启动类,看是否启动正常~

/**
 * @Author 
 * @Description 启动类
 * @Date
 */
// @SpringCloudApplication 被弃用
@SpringBootApplication
public class OauthServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(OauthServerApplication.class, args);
    }
}
复制代码


在新版本中@SpringCloudApplication被弃用了,我们直接使用@SpringBootApplication就好了,你会发现,啥都不加服务也会自动注册,说明新版本中已经比较完善了。@SpringCloudApplication本质上也就是在@SpringBootApplicaiton中加了几个注解

@Deprecated
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
public @interface SpringCloudApplication {
}
复制代码

启动一下,如果正常说明第一步我们就成功了~ 下面我们进入正题


搭建 OAuth2 Server

首先我们要去搭建一个认证服务配置,Oauth2有多种模式,常见的有密码模式还有授权码模式,现在比较流行的就是授权码模式,下面要带大家实现的就是基于授权码模式


认证服务配置

/**
 * @Author qcl
 * @Description Oauth2 认证服务配置
 * @Date
 */
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
    // 认证管理器
    @Autowired
    private AuthenticationManager authenticationManager;
    // 数据源
    @Autowired
    private DataSource dataSource;
    //密码加密方式
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    // 自定义身份认证
    @Autowired
    private UserServiceImpl userDetailsService;
    @Bean
    public ClientDetailsService jdbcClientDetailsService(){
        //存储client信息
        return new JdbcClientDetailsService(dataSource);
    }
    @Bean
    public TokenStore tokenStore(){
        // token存储
        return new JdbcTokenStore(dataSource);
    }
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        // 授权码模式
        return new JdbcAuthorizationCodeServices(dataSource);
    }
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //配置 client信息从数据库中取
        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,允许请求
        security.tokenKeyAccess("permitAll()") // 开启/oauth/token_key 验证端口-无权限
                .checkTokenAccess("isAuthenticated()") // 开启/oauth/check_token 验证端口-需权限
                .allowFormAuthenticationForClients()// 允许表单认证
                .passwordEncoder(passwordEncoder());   // 配置BCrypt加密
    }
}
复制代码


这里我们使用基于持久化的配置,相关注释已经加上了,那么它的数据存在哪呢?它是存在数据库中,下面我们需要准备几张表

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 utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `token` blob NULL,
  `authentication_id` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `user_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `client_id` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `authentication` blob NULL,
  `refresh_token` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '存储生成的access_token' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of oauth_access_token
-- ----------------------------
-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
  `resource_ids` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `scope` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `authorities` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `access_token_validity` int NULL DEFAULT NULL,
  `refresh_token_validity` int NULL DEFAULT NULL,
  `additional_information` text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL,
  `autoapprove` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT 'false',
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '存储客户端的配置信息' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details` VALUES ('clientId1', '1', '$2a$10$WFDhpNunaH0aPlPQp33q8OAlqot6NANran7HeP/DSSWt1kVRvF2d2', 'all', 'password,refresh_token,authorization_code,implicit,client_credentials', 'https://www.baidu.com', 'ROLE_ADMIN', 604800, 1209600, '{\"info\": \"test\"}', 'false');
-- ----------------------------
-- Table structure for oauth_code
-- ----------------------------
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code`  (
  `code` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '存储服务端系统生成的code的值' 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 utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `token` blob NULL,
  `authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '存储刷新令牌的refresh_token' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of oauth_refresh_token
-- ----------------------------
SET FOREIGN_KEY_CHECKS = 1;
复制代码


默认给大家已经插入了一条客户端的配置,后边我们会用到它

在代码中,我们可以看到导入了一个UserDetailsService,这个类需要我们实现它

/**
 * @Author qcl
 * @Description 用户信息
 * @Date
 */
@Slf4j
@Service
public class UserServiceImpl implements UserDetailsService {
    @Autowired
    private SysUserService sysUserService;
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        UserInfo userInfo = sysUserService.getUserByUserName(userName);
        log.warn("user: " + userInfo.getUsername());
        if (userInfo == null){
            throw new UsernameNotFoundException("用户不存在: " + userName);
        }
        List<GrantedAuthority> authorities=new ArrayList<>();
        //获取用户权限
        List<String> permissions = userInfo.getPermissions();
        permissions.forEach(permission->{
            authorities.add(new SimpleGrantedAuthority("ROLE_" + permission));
        });
        // 这里一定要基于 BCrypt 加密,不然会不通过
        UserDetails user = new User(userInfo.getUsername(), new BCryptPasswordEncoder().encode(userInfo.getPassword()), authorities);
        log.warn(user.toString());
        return user;
    }
}
复制代码


它主要是用来验证和存储用户信息的,场景就是用户在进行登录授权的时候会走到这里边,我们可以在这进行用户身份校验,SysUserService也是我们自己实现的,用来获取用户信息,这里为了方便演示,直接模拟了

@Service
public class SysUserServiceImpl implements SysUserService {
    @Override
    public UserInfo getUserByUserName(String userName) {
        ArrayList<String> list = new ArrayList<>();
        list.add("ADMIN");
        UserInfo user = new UserInfo();
        user.setUserId("1111");
        user.setPassword("123321");
        user.setUsername(userName);
        user.setPermissions(list);
        return user;
    }
}
复制代码


这里给大家提示下可能遇到的坑, 就是userInfo.getPassword(), 如果你是从数据库读的,如果使用了BCryptPasswordEncoder加密过的,那么可以,如果不是的话,你就需要BCryptPasswordEncoder加密一下,不过这样不大安全,推荐是大家在数据库加密,我这里为了方便给大家演示


开启Spring Security配置

下面我们整一下安全配置

/**
 * @Author 
 * @Description 开启Spring Security配置
 * @Date
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/oauth/check_token");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
                .antMatchers("/view/**").permitAll()
                .anyRequest().authenticated();
    }
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
复制代码


认证流程

现在我们就可以启动它了,然后到浏览器访问http://localhost:10001/oauth/authorize?client_id=clientId1&response_type=code&scope=all&redirect_uri=https://www.baidu.com


有几个参数,给大家解释一下:

  • /oauth/authorize 这个是Ouath2默认开启的规范接口,通过它进行客户端验证
  • client_id 客户端ID, 也就是分配给第三方的认证ID,
  • response_type=code 返回的类型code,也就是我们开启的是授权码模式
  • scope 授权范围, 需要和我们数据库配置的一致
  • redirect_uri 认证成功后重定向的地址,它会携带code 参数


说了这些,一开始接触的朋友,可能还是不大清除,下面给一张图给大家体会一下:

微信截图_20230209174650.png


如果有朋友接过h5微信的登录,相必会深有体会


下面,我们就去访问它。当我们在浏览器输入地址的时候会自动跳转到/login的地址页面。这个页面是框架自带的,有时候,我们有很多个性化的需求需要自定义,下面就带大家整一下自定义的登录和授权页面


自定义授权和登录页

配置也很简单,我们只需要修改WebSecurityConfig的配置

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
                .antMatchers("/view/**", "/user/**").permitAll()
                .anyRequest().authenticated();
        http.formLogin()
                // 自定义处理登录逻辑的地址login
                .loginProcessingUrl("/login")
                // 自定义登录页面
                .loginPage("/view/login")
                .permitAll()
                .and()
                .csrf().disable().httpBasic();
    }
复制代码


这里,我已经准备好了模板,我们使用thymeleaf简单的给大家演示一下

/**
 * @Author 
 * @Description
 * @Date
 */
@Controller
@RequestMapping("/view")
public class ViewController {
    @RequestMapping("/login")
    public String loginView(Model model) {
        return "oauth-login";
    }
}
复制代码


  • templates/oauth-login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www/thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<div>
    <h1>授权登录页</h1>
    <br/>
    <form name="loginForm" method="post" action="/login">
        用户名: <input type="text" name="username" placeholder="用户名" />
        <br/>
        密码: <input type="password" name="password" placeholder="密码" />
        <button type="submit">点击登录</button>
    </form>
</div>
</body>
</html>
复制代码


因为我们引入的是```spring-boot-starter-thymeleaf````,所以默认给我们配置好了,直接添加html就可以了,下面我们再自定义一下授权页,授权页就是比如你用qq登录了网站, 你扫码成功了或者密码输入整个了,这时候会提示获取你的信息,你同意了才可以继续操作


自定义授权页,相对复杂一点,如果对流程和源码不咋熟的话,有点困难


首先需要我们改一下Oauth2ServerConfig的配置,因为授权在这里

@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .tokenStore(tokenStore())//token存储方式
                .authenticationManager(authenticationManager)// 开启密码验证,由 WebSecurityConfigurerAdapter
                .userDetailsService(userDetailsService)// 读取验证用户信息
                .authorizationCodeServices(authorizationCodeServices())
                // 自定义授权跳转
                .pathMapping("/oauth/confirm_access", "/custom/confirm_access")
                // 自定义异常跳转
                .pathMapping("/oauth/error", "/view/oauth/error")
                .setClientDetailsService(jdbcClientDetailsService());
    }
复制代码


默认下是"/oauth/confirm_access,我们替换成自定义的/custom/confirm_access,同样的自定义异常的页面也是可以的。下一步,我们需要加个控制器AuthorizeController

@SessionAttributes("authorizationRequest")
@Controller
public class AuthorizeController {
    @RequestMapping("/custom/confirm_access")
    public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
        AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");
        ModelAndView view = new ModelAndView();
        view.setViewName("oauth-authorize");
        view.addObject("clientId", authorizationRequest.getClientId());
        view.addObject("scopes",authorizationRequest.getScope());
        return view;
    }
}
复制代码


紧接着就是模板文件 oauth-authorize.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www/thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>授权页</title>
</head>
<body>
<div>
    <h3 th:text="${clientId}+' 请求授权,该应用将获取你的以下信息'"></h3>
    <p>昵称,头像和性别</p>
    授权后表明你已同意 <a  href="#boot" style="color: #E9686B">服务协议</a>
    <form method="post" action="/oauth/authorize">
        <input type="hidden" name="user_oauth_approval" value="true">
        <div th:each="item:${scopes}">
            <input type="radio" th:name="'scope.'+${item}" value="true" checked>同意
            <input type="radio" th:name="'scope.'+${item}" value="false" >拒绝
        </div>
        <input name="authorize" value="同意/授权" type="submit">
    </form>
</div>
</body>
</html>
复制代码


这一步,你可以向用户展示信息,这里就给大家简单写写,我们主要体验功能, 好,有了这些准备之后,我们可以正式进入测试环节了


测试环节

首先访问http://localhost:10001/oauth/authorize?client_id=clientId1&response_type=code&scope=all&redirect_uri=https://www.baidu.com,然后自动跳转到/view/login,输入完用户名和密码会跳转到授权页

微信截图_20230209174752.png


也就是/custom/confirm_access

微信截图_20230209174758.png


点击同意之后会跳转到https://www.baidu.com?code=xxx,会携带code参数,下面我们就可以通过code获取access_token了,获取token````的地址,http://localhost:10001/oauth/token```,使用```POST```请求,因为我们开启了``` .allowFormAuthenticationForClients()// 允许表单认证```,所以可以进行表单请求,如果没开启的话,是没法的。

微信截图_20230209174836.png


我们需要拿着secret去请求,也就是数据库存储的client_secret,这个一般由服务提供者提供,也就是所谓的秘钥


成功之后会返回```access_token``,到这里我们就算搭建成功了

微信截图_20230209174903.png


说一下这里大家可能遇到的坑,如果你遇到了not like BCrypt 的报错,然后明明你的代码配置都对,可怎么改都没有用,八成是你的secret不对,首先排查你传的secret,这个时没有BCrypt过的,其次就是数据库存储的secret这里不能明文存储,需要是加密过的,这个也很简单,我们本地加密一下存进去就行了

public class BCryptTest {
    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123321"));
    }
}
复制代码


结束语

本节到这里就结束了,下节带着大家看一下微服务之间如何统一认证统一鉴权,如何统一管理我们的资源服务,如何加入jwt支持以及整合我们的网关服务。关注我,不迷路 ~

相关文章
|
19天前
|
数据管理 Nacos 开发者
"Nacos架构深度解析:一篇文章带你掌握业务层四大核心功能,服务注册、配置管理、元数据与健康检查一网打尽!"
【10月更文挑战第23天】Nacos 是一个用于服务注册发现和配置管理的平台,支持动态服务发现、配置管理、元数据管理和健康检查。其业务层包括服务注册与发现、配置管理、元数据管理和健康检查四大核心功能。通过示例代码展示了如何在业务层中使用Nacos,帮助开发者构建高可用、动态扩展的微服务生态系统。
63 0
|
19天前
|
SQL 关系型数据库 数据库连接
"Nacos 2.1.0版本数据库配置写入难题破解攻略:一步步教你排查连接、权限和配置问题,重启服务轻松解决!"
【10月更文挑战第23天】在使用Nacos 2.1.0版本时,可能会遇到无法将配置信息写入数据库的问题。本文将引导你逐步解决这一问题,包括检查数据库连接、用户权限、Nacos配置文件,并提供示例代码和详细步骤。通过这些方法,你可以有效解决配置写入失败的问题。
44 0
|
2月前
|
负载均衡 Java Nacos
SpringCloud基础2——Nacos配置、Feign、Gateway
nacos配置管理、Feign远程调用、Gateway服务网关
SpringCloud基础2——Nacos配置、Feign、Gateway
|
2月前
|
Java 开发者 Spring
Spring Cloud Gateway 中,过滤器的分类有哪些?
Spring Cloud Gateway 中,过滤器的分类有哪些?
48 3
|
2月前
|
负载均衡 Java 网络架构
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
102 5
|
1月前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
37 0
|
2月前
|
安全 Java 开发者
强大!Spring Cloud Gateway新特性及高级开发技巧
在微服务架构日益盛行的今天,网关作为微服务架构中的关键组件,承担着路由、安全、监控、限流等多重职责。Spring Cloud Gateway作为新一代的微服务网关,凭借其基于Spring Framework 5、Project Reactor和Spring Boot 2.0的强大技术栈,正逐步成为业界的主流选择。本文将深入探讨Spring Cloud Gateway的新特性及高级开发技巧,助力开发者更好地掌握这一强大的网关工具。
219 6
|
11天前
|
负载均衡 应用服务中间件 Nacos
Nacos配置中心
Nacos配置中心
41 1
Nacos配置中心
|
4月前
|
NoSQL Java Nacos
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
137 3
|
7天前
|
监控 Java 测试技术
Nacos 配置中心变更利器:自定义标签灰度
本文是对 MSE Nacos 应用自定义标签灰度的功能介绍,欢迎大家升级版本进行试用。