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

总概

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、本节实现目标

  • 新建mall-auth服务,完成授权功能

一、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即可。


    org.springframework.cloud
    spring-cloud-starter-oauth2
    2.2.4.RELEASE

三、架构说明

认证服务(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



    4.0.0
    
        mall-pom
        com.ac
        1.0-SNAPSHOT
    

    com.ac
    mall-oauth2-module
    ${mall.version}
    mall-oauth2-module
    oauth2模块

    

        
            org.springframework.security.oauth
            spring-security-oauth2
            2.3.4.RELEASE
        

    

    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                
                    8
                    8
                
            
        
    

4.1.2 项目结构

微服务开发系列 第九篇:OAuth2_第1张图片

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



    4.0.0
    
        mall-pom
        com.ac
        1.0-SNAPSHOT
    

    com.ac
    mall-auth
    ${mall.version}
    mall-auth
    授权服务

    
        
            com.ac
            mall-core
            1.0-SNAPSHOT
        

        
            com.ac
            mall-common
            1.0-SNAPSHOT
        

        
            com.ac
            mall-oauth2
            1.0-SNAPSHOT
        

        
            org.springframework.cloud
            spring-cloud-starter-oauth2
        

        
            com.nimbusds
            nimbus-jose-jwt
            9.31
        
    

    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                
                    8
                    8
                
            
        
    

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 params = getMemberBaseParam(vo, SecurityLoginTypeEnum.APP_PWD.getCode());
        params.put("mobile", vo.getMobile());
        params.put("password", vo.getPassword());
        List 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 params = getMemberBaseParam(vo, SecurityLoginTypeEnum.APP_SMS.getCode());
            params.put("globalCode", vo.getGlobalCode());
            params.put("mobile", vo.getMobile());
            params.put("code", vo.getMsgCode());
            List 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 params = getMemberBaseParam(vo, SecurityLoginTypeEnum.APP_ONE_KEY.getCode());
        params.put("token", vo.getToken());
        List 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 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 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 params = getMemberBaseParam(vo, SecurityLoginTypeEnum.APP_ANONYMOUS.getCode());
        List grantedAuthorities = new ArrayList<>();
        Oauth2TokenDTO oauth2Token = authTokenComponent.getAccessToken(vo.getClientId(), "app", grantedAuthorities, params);
        return oauth2Token;
    }

    private Map getMemberBaseParam(MemberLoginBaseVO vo, String grantType) {
        Map 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;
    }
}

微服务开发系列 第九篇:OAuth2_第2张图片

mall-auth项目结构

五、测试

5.1 账号密码

微服务开发系列 第九篇:OAuth2_第3张图片

账号密码

5.2 短信验证码

微服务开发系列 第九篇:OAuth2_第4张图片短信验证码

你可能感兴趣的:(微服务,微服务,java,架构,mall-auth服务,授权)