美文网首页一些收藏
微服务开发系列 第九篇:OAuth2

微服务开发系列 第九篇:OAuth2

作者: AC编程 | 来源:发表于2023-05-03 14:52 被阅读0次

总概

A、技术栈
  • 开发语言:Java 1.8
  • 数据库:MySQL、Redis、MongoDB、Elasticsearch
  • 微服务框架:Spring Cloud Alibaba
  • 微服务网关:Spring Cloud Gateway
  • 服务注册和配置中心:Nacos
  • 分布式事务:Seata
  • 链路追踪框架:Sleuth
  • 服务降级与熔断:Sentinel
  • ORM框架:MyBatis-Plus
  • 分布式任务调度平台:XXL-JOB
  • 消息中间件:RocketMQ
  • 分布式锁:Redisson
  • 权限:OAuth2
  • DevOps:Jenkins、Docker、K8S
B、源码地址

alanchenyan/ac-mall2-cloud

C、本节实现目标
  • 新建mall-auth服务,完成授权功能
D、系列

一、OAuth2.0介绍

OAuth(开发授权)是一个开放标准,允许用户授权第三方应用,访问他们存储在另外的服务提供者上的信息。而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。OAuth2.0是OAuth协议的延续版,但不兼容OAuth1.0(OAuth1.0已被完全废止)。

二、spring-cloud-starter-oauth2使用

2.1 Spring-Security-OAuth2介绍

Spring Security OAuth2是对OAuth2.0协议的一种实现,并且和Spring Sercurity相辅相成,属于Spring Cloud的体系,与Spring Boot的集成相当便利。在OAuth2.0的协议里包括两个服务提供方,授权服务(也叫认证服务)、资源服务。使用Spring Security OAuth2的时候可以把这两个服务放到同一个应用里面(生产环境不会这样干),也可以建立一个授权服务,对多个资源服务进行授权。

2.2 pom.xml依赖

SpringCloud微服务全家桶中有spring-cloud-starter-security依赖组件,并且spring-cloud-starter-oauth2依赖了spring-cloud-starter-securityspring-cloud-starter-security依赖了spring-boot-starter-security,因此添加spring-cloud-starter-oauth2即可。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
    <version>2.2.4.RELEASE</version>
</dependency>

三、架构说明

认证服务(mall-auth)负责认证授权,网关服务(mall-gateway)负责校验认证和鉴权,其他API服务负责处理自己的业务逻辑。安全相关的逻辑只存在于认证服务和网关服务中,其他服务只是单纯地提供服务而没有任何安全相关逻辑。

具体服务:

  • [mall-auth]:认证服务,负责对登录用户进行认证授权颁发token。
  • [mall-gateway]:网关服务,负责请求转发和校验认证和鉴权。
  • [mall-member]:受保护的API服务,用户鉴权通过后可以访问该服务,该类服务还有[mall-product]、[mall-search]等等。

四、代码实现

4.1 mall-oauth2-module模块

新建mall-oauth2-module模块,该模块被mall-auth和后面的mall-gateway所依赖。

4.1.1 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>
    <parent>
        <artifactId>mall-pom</artifactId>
        <groupId>com.ac</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.ac</groupId>
    <artifactId>mall-oauth2-module</artifactId>
    <version>${mall.version}</version>
    <name>mall-oauth2-module</name>
    <description>oauth2模块</description>

    <dependencies>

        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.4.RELEASE</version>
        </dependency>

    </dependencies>

    <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>
</project>
4.1.2 项目结构
mall-oauth2-module

具体实现代码可参看源码。

4.2 mall-auth服务

新建mall-auth服务,接入OAuth2,颁发token,将token存在redis。

4.2.1 数据库脚本
CREATE TABLE `oauth_client_details` (
    `client_id` VARCHAR(128)CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
    `resource_ids` VARCHAR(256)CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
    `client_secret` VARCHAR(256)CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
    `scope` VARCHAR(256)CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
    `authorized_grant_types` VARCHAR(256)CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
    `web_server_redirect_uri` VARCHAR(256)CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
    `authorities` VARCHAR(256)CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
    `access_token_validity` INT DEFAULT NULL,
    `refresh_token_validity` INT DEFAULT NULL,
    `additional_information` VARCHAR(4096)CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
    `autoapprove` VARCHAR(256)CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
    `client_secret_str` VARCHAR(20)CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
    PRIMARY KEY (`client_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;

INSERT INTO `oauth_client_details` VALUES ('app',NULL,'$2a$10$i3F515wEDiB4Gvj9ym9Prui0dasRttEUQ9ink4Wpgb4zEDCAlV8zO','all','APP_SMS,APP_ONE_KEY,APP_SOCIAL,APP_PWD,ADMIN_PWD,APP_SOCIAL,APP_ANONYMOUS,APP_QRCODE,authorization_code,password,refresh_token','http://a.qimiao.com',NULL,2592000,2592000,NULL,NULL,'app'),('h5',NULL,'$2a$10$i3F515wEDiB4Gvj9ym9Prui0dasRttEUQ9ink4Wpgb4zEDCAlV8zO','all','APP_SMS,APP_ONE_KEY,APP_SOCIAL,APP_PWD,ADMIN_PWD,APP_SOCIAL,APP_ANONYMOUS,APP_QRCODE,authorization_code,password,refresh_token',NULL,NULL,2592000,2592000,NULL,NULL,'app'),('mini',NULL,'$2a$10$i3F515wEDiB4Gvj9ym9Prui0dasRttEUQ9ink4Wpgb4zEDCAlV8zO','all','APP_SMS,APP_ONE_KEY,APP_SOCIAL,APP_PWD,ADMIN_PWD,APP_SOCIAL,APP_ANONYMOUS,APP_QRCODE,authorization_code,password,refresh_token',NULL,NULL,2592000,2592000,NULL,NULL,'app'),('web',NULL,'$2a$10$i3F515wEDiB4Gvj9ym9Prui0dasRttEUQ9ink4Wpgb4zEDCAlV8zO','all','APP_SMS,APP_ONE_KEY,APP_SOCIAL,APP_PWD,ADMIN_PWD,APP_SOCIAL,APP_ANONYMOUS,APP_QRCODE,authorization_code,password,refresh_token',NULL,NULL,2592000,2592000,NULL,NULL,'app');
4.2.2 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>
    <parent>
        <artifactId>mall-pom</artifactId>
        <groupId>com.ac</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.ac</groupId>
    <artifactId>mall-auth</artifactId>
    <version>${mall.version}</version>
    <name>mall-auth</name>
    <description>授权服务</description>

    <dependencies>
        <dependency>
            <groupId>com.ac</groupId>
            <artifactId>mall-core</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.ac</groupId>
            <artifactId>mall-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.ac</groupId>
            <artifactId>mall-oauth2</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>9.31</version>
        </dependency>
    </dependencies>

    <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>
</project>
4.2.3 AuthController
package com.ac.auth.controller;

import com.ac.auth.component.AuthRedisHelper;
import com.ac.auth.component.AuthTokenComponent;
import com.ac.auth.dto.Oauth2TokenDTO;
import com.ac.oauth2.enums.SecurityLoginTypeEnum;
import com.ac.auth.util.IpUtil;
import com.ac.auth.vo.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import jodd.util.StringUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Api(tags = "会员授权")
@RestController
@RequestMapping("oauth")
public class AuthController {

    @Resource
    private AuthTokenComponent authTokenComponent;

    @Resource
    private AuthRedisHelper authRedisHelper;

    @Resource
    private RedissonClient redissonClient;

    @SneakyThrows
    @PostMapping("pwdLogin")
    @ApiOperation(value = "密码登录")
    public Oauth2TokenDTO pwdLogin(@RequestBody MemberLoginPwdVO vo, HttpServletRequest request) {
        if (StringUtil.isEmpty(vo.getClientId())) {
            vo.setClientId("app");
        }
        vo.setIp(IpUtil.ip(request));
        Map<String, String> params = getMemberBaseParam(vo, SecurityLoginTypeEnum.APP_PWD.getCode());
        params.put("mobile", vo.getMobile());
        params.put("password", vo.getPassword());
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        Oauth2TokenDTO oauth2Token = authTokenComponent.getAccessToken(vo.getClientId(), "app", grantedAuthorities, params);
        return oauth2Token;
    }

    @SneakyThrows
    @PostMapping("msgCodeLogin")
    @ApiOperation(value = "短信验证码登录")
    public Oauth2TokenDTO msgCodeLogin(@RequestBody MemberLoginMsgCodeVO vo, HttpServletRequest request) {
        log.info("msgCodeLogin:mobile={}", vo.getMobile());
        RLock rdsLock = redissonClient.getLock(vo.getMobile());
        try {
            rdsLock.lock(5, TimeUnit.SECONDS);
            Boolean delRecord = authRedisHelper.getDelRecord(vo.getMobile());
            if (delRecord) {
                throw new RuntimeException("用户已注销");
            }

            vo.setIp(IpUtil.ip(request));
            Map<String, String> params = getMemberBaseParam(vo, SecurityLoginTypeEnum.APP_SMS.getCode());
            params.put("globalCode", vo.getGlobalCode());
            params.put("mobile", vo.getMobile());
            params.put("code", vo.getMsgCode());
            List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
            Oauth2TokenDTO oauth2Token = authTokenComponent.getAccessToken(vo.getClientId(), "app", grantedAuthorities, params);
            return oauth2Token;
        } finally {
            // 释放锁
            if (rdsLock.isLocked()) {
                rdsLock.unlock();
            }
        }
    }

    @SneakyThrows
    @PostMapping("oneKeyLogin")
    @ApiOperation(value = "一键登录")
    public Oauth2TokenDTO oneKeyLogin(@RequestBody MemberLoginOneKeyVO vo, HttpServletRequest request) {
        vo.setIp(IpUtil.ip(request));
        Map<String, String> params = getMemberBaseParam(vo, SecurityLoginTypeEnum.APP_ONE_KEY.getCode());
        params.put("token", vo.getToken());
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        Oauth2TokenDTO oauth2Token = authTokenComponent.getAccessToken(vo.getClientId(), "app", grantedAuthorities, params);
        return oauth2Token;
    }

    @SneakyThrows
    @PostMapping("socialLogin")
    @ApiOperation(value = "第三方登录")
    public Oauth2TokenDTO socialLogin(@RequestBody MemberLoginSocialVO vo, HttpServletRequest request) {
        RLock rdsLock = redissonClient.getLock(vo.getAcc());
        try {
            rdsLock.lock(5, TimeUnit.SECONDS);
            if (StringUtil.isEmpty(vo.getClientId())) {
                vo.setClientId("app");
            }
            vo.setIp(IpUtil.ip(request));
            Map<String, String> params = getMemberBaseParam(vo, SecurityLoginTypeEnum.APP_SOCIAL.getCode());
            params.put("platform", vo.getPlatform());
            params.put("socialType", vo.getSocialType().getCode());
            params.put("acc", vo.getAcc());
            params.put("uid", vo.getUid());
            params.put("iconUrl", vo.getIconUrl());
            params.put("nickName", vo.getNickName());

            List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
            Oauth2TokenDTO oauth2Token = authTokenComponent.getAccessToken(vo.getClientId(), "app", grantedAuthorities, params);
            return oauth2Token;
        } finally {
            // 释放锁
            if (rdsLock.isLocked()) {
                rdsLock.unlock();
            }
        }
    }

    @SneakyThrows
    @PostMapping("visitor")
    @ApiOperation(value = "游客登录")
    public Oauth2TokenDTO visitorLogin(@RequestBody MemberLoginVisitorVO vo, HttpServletRequest request) {
        vo.setIp(IpUtil.ip(request));
        Map<String, String> params = getMemberBaseParam(vo, SecurityLoginTypeEnum.APP_ANONYMOUS.getCode());
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        Oauth2TokenDTO oauth2Token = authTokenComponent.getAccessToken(vo.getClientId(), "app", grantedAuthorities, params);
        return oauth2Token;
    }

    private Map<String, String> getMemberBaseParam(MemberLoginBaseVO vo, String grantType) {
        Map<String, String> params = new HashMap<>();
        params.put("client_id", vo.getClientId());
        params.put("client_secret", "app");
        params.put("grant_type", grantType);
        params.put("scope", "all");
        params.put("platform", vo.getPlatform());
        //附加信息
        params.put("version", vo.getVersion());
        params.put("device", vo.getDevice());
        params.put("iemi", vo.getIemi());
        params.put("location", vo.getLocation());
        params.put("ip", vo.getIp());
        params.put("recommendCode", vo.getRecommendCode());
        return params;
    }
}
mall-auth项目结构

五、测试

5.1 账号密码
账号密码
5.2 短信验证码
短信验证码

相关文章

网友评论

    本文标题:微服务开发系列 第九篇:OAuth2

    本文链接:https://www.haomeiwen.com/subject/belcjdtx.html