【OAuth2】用户授权第三方应用,使用授权码模式实例案例进行详解(源代码)

2023-12-28 11:30:22

目录

一、引言

1. 详解

2. 实现流程

3. 实例说明?

二、实例案例

1. 数据表

2. 项目结构

3. 所需依赖及生成?

4. 授权处理

每篇一获


一、引言

1. 详解

授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。

这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。

适用场景 :?

1. Web应用程序:当客户端是一个运行在Web服务器上的应用程序时,授权码模式是一个常见的选择。用户在Web应用程序中进行登录和授权,然后客户端使用授权码来获取访问令牌。

2. 移动应用程序:对于需要访问受保护资源的移动应用程序,授权码模式也是一个很好的选择。用户可以在移动应用程序中进行登录和授权,然后客户端使用授权码来获取访问令牌。

3. 第三方应用程序:当一个第三方应用程序需要代表用户访问受保护资源时,授权码模式也可以使用。用户可以在授权服务器上进行登录和授权,然后第三方应用程序使用授权码来获取访问令牌。

总之,授权码模式适用于需要在用户和客户端之间建立信任关系,并且需要通过授权码来获取访问令牌的场景。它是一种相对安全的授权方式,适用于多种不同类型的应用程序。

2. 实现流程

主要流程?:

  1. 用户访问客户端,客户端将用户重定向到授权服务器。

  2. 用户在授权服务器上登录并同意授权客户端请求的权限。

  3. 授权服务器将用户重定向回客户端,并附上一个授权码。

  4. 客户端收到授权码后,使用该授权码向授权服务器请求访问令牌。

  5. 客户端向授权服务器发送包含授权码的请求,并提供客户端凭证(客户端ID和客户端密钥)。

  6. 授权服务器验证客户端凭证和授权码,如果验证通过,授权服务器将颁发访问令牌给客户端。

  7. 客户端使用访问令牌向受保护资源服务器请求受保护资源。

通过以上流程,客户端可以通过授权码模式获取访问令牌,并使用该访问令牌访问受保护资源。这种方式可以保护用户的敏感信息,同时也可以保护客户端的凭证信息。

3. 实例说明?

请求示例:

  • 步骤 1 :客户端申请认证的URI

https://www.cloudjun.com/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL

&scope=read&state=xxx

参数说明:

参数类型说明
response_type授权类型,必选项,此处的值固定为"code"
client_id客户端的ID,必选项
redirect_uri重定向URI,认证服务器接受请求之后的调转连接,可以根据这个连接将生成的授权码回传,必选项
scopecode发送给资源服务器申请的权限范围,可选项
state任意值,认证服务器会原样返回,用于抵制CSRF(跨站请求伪造)攻击。
  • 步骤 3 :服务器回应客户端的URI

https://client.cloudjun.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xxx

参数说明:

参数类型说明
code授权码,必选项。授权码有效期通常设为10分钟,一次性使用。该码与客户端ID、重定向URI以及用户,是一一对应关系。
state原样返回客户端传的该参数的值
  • 步骤 4 :客户端向认证服务器申请令牌

https://www.cloudjun.com/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&

grant_type=authorization_code&code=AUTHORIZATION_CODE&

redirect_uri=CALLBACK_URL

?参数说明:

参数类型说明
client_id表示客户端ID,必选项
client_secret表示安全参数,只能在后端发请求
grant_type表示使用的授权模式,必选项,此处的值固定为"authorization_code"
code表示上一步获得的授权码,必选项
redirect_uri表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致
  • 步骤 5 :响应步骤(4)的数据

{?
?? ?"access_token":访问令牌,?
?? ?"token_type":"bearer",?
?? ?"expires_in":过期时间,?
?? ?"refresh_token":"REFRESH_TOKEN",?
?? ?"scope":"read",?
?? ?"uid":用户ID,
? ? "info":{...}?
}

??参数说明:

参数类型说明
access_token访问令牌,必选项
token_type令牌类型,该值大小写不敏感,必选项
expires_in过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间
refresh_token更新令牌,用来获取下一次的访问令牌,可选项
scope权限范围,如果与客户端申请的范围一致,此项可省略

二、实例案例

以下代码及操作有不理解的可以看我所写的"实现流程"及"实例说明"进行代码结合理解

1. 数据表

首先需要创建数据表,根据以下表格及字段创建即可,或有更好的也可

2. 项目结构

如何我们使用Mybatis-plus来生成代码,编写一个代码生成来。

这个代码生成类,两个小项目中都需要有。

MySQLGenerator

package com.wx.server.config;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Slf4j
public class MySQLGenerator {

    private final static String URL = "jdbc:mysql://localhost:3306/oauth?serverTimezone=GMT";
    private final static String USERNAME = "root";
    private final static String PASSWORD = "123456";

    private final static DataSourceConfig.Builder DATA_SOURCE_CONFIG =
            new DataSourceConfig.Builder(URL, USERNAME, PASSWORD);

    public static void main(String[] args) {
        FastAutoGenerator.create(DATA_SOURCE_CONFIG)
                .globalConfig(
                        (scanner, builder) ->
                                builder.author(scanner.apply("请输入作者名称?"))
                                        .outputDir(System.getProperty("user.dir") + "\\wx-server\\src\\main\\java")
                                        .commentDate("yyyy-MM-dd")
                                        .dateType(DateType.TIME_PACK)
                )
                .packageConfig((builder) ->
                        builder.parent("com.wx.server")
                                .entity("pojo")
                                .service("service")
                                .serviceImpl("service.impl")
                                .mapper("mapper")
                                .xml("mapper.xml")
                                .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "\\src\\main\\resources\\mapper"))
                )
                .injectionConfig((builder) ->
                        builder.beforeOutputFile(
                                (a, b) -> log.warn("tableInfo: " + a.getEntityName())
                        )
                )
                .strategyConfig((scanner, builder) ->
                        builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
                                .addTablePrefix("tb_", "t_", "lay_", "meeting_", "sys_", "t_medical_", "oath_")
                                .entityBuilder()
                                .enableChainModel()
                                .enableLombok()
                                .enableTableFieldAnnotation()
                                .controllerBuilder()
                                .enableRestStyle()
                                .enableHyphenStyle()
                                .build()
                )
                .templateEngine(new FreemarkerTemplateEngine())
                .execute();
    }

    protected static List<String> getTables(String tables) {
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }

}

?Oauth大项目中的pom.xml依赖有以下 :?

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         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>org.example</groupId>
    <artifactId>Oauth</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>zking-server</module>
        <module>wx-server</module>
    </modules>

    <properties>
        <oauth2.version>0.31</oauth2.version>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.1</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.yitter</groupId>
            <artifactId>yitter-idgenerator</artifactId>
            <version>1.0.6</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.oltu.oauth2</groupId>
                <artifactId>org.apache.oltu.oauth2.client</artifactId>
                <version>${oauth2.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.oltu.oauth2</groupId>
                <artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
                <version>${oauth2.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.oltu.oauth2</groupId>
                <artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
                <version>${oauth2.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

3. 所需依赖及生成?

在wx-server 的项目中,生成根据数据表中的oath_user,oath_company表

其中项目的pom.xml依赖有以下 :?

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         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>
    <groupId>com.example</groupId>
    <artifactId>wx-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <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>

    <parent>
        <artifactId>Oauth</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.apache.oltu.oauth2</groupId>
            <artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.oltu.oauth2</groupId>
            <artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>
    </dependencies>

</project>

在wx-server 的项目中

依赖导入完成后就生成代码。oath_user,oath_company

不知道如何生成代码可以看我博客中写的 :?

Mybatis-plus是使用,告别繁琐的CRUD编写,自动生成直接使用icon-default.png?t=N7T8https://blog.csdn.net/SAME_LOVE/article/details/134979107?spm=1001.2014.3001.5501? 其 搭建使用 中的 生成。

在wx-server 的项目中 的application.yml文件配置?

spring:
  freemarker:
    suffix: .ftl
    template-loader-path: classpath:/templates/
    enabled: true
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/oauth?serverTimezone=GMT
  redis:
    host: localhost
    port: 6379
    database: 0
server:
  port: 9999

在 zking-server 的项目中,生成根据数据表中的?zking_user?表

其中项目的pom.xml依赖有以下 :?

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         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>
    <groupId>com.example</groupId>
    <artifactId>zking-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <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>

    <parent>
        <artifactId>Oauth</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.apache.oltu.oauth2</groupId>
            <artifactId>org.apache.oltu.oauth2.client</artifactId>
        </dependency>
    </dependencies>

</project>

在 zking-server 的项目中 ,?依赖导入完成后就生成代码。zking_user?

在 zking-server 的项目中 ,?之后在实体包中创建一个User实体 :?

package com.zking.server.pojo;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Getter
@Setter
@Accessors(chain = true)
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String id;

    private String nickName;

    private String realName;

    private String bankId;

    private String openId;

    private String account;

    private String password;

    private String avatar;

}

在 zking-server 的项目中 的application.yml文件配置

spring:
  freemarker:
    suffix: .ftl
    template-loader-path: classpath:/templates/
    enabled: true
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/oauth?serverTimezone=GMT
server:
  port: 8080

在 zking-server 的项目中的resources文件中创建templates文件,再分别创建login.ftl

phone.ftl

login.ftl

<!doctype html>
<html lang="zh">
<head>
    <title>Document</title>
</head>
<body>
<form action="${springMacroRequestContext.contextPath}/client/login">
    <p>账户:<input type="text" name="account"></p>
    <p>密码:<input type="password" name="password"></p>
    <p>
        <button>登录</button>
        <a href="${springMacroRequestContext.contextPath}/client/wx">微信登录</a>
    </p>
</form>
</body>
</html>

phone.ftl

<!doctype html>
<html lang="zh">
<head>
    <title>Document</title>
</head>
<body>
<form action="">
    <input name="nickName">
    <button>绑定</button>
</form>
</body>
</html>

在 wx-server 的项目中的resources文件中创建templates文件,再分别创建login.ftl

error.ftl及auth.ftl

login.ftl

<!doctype html>
<html lang="zh">
<head>
    <title>Document</title>
</head>
<body>
<form action="${springMacroRequestContext.contextPath}/auth/doLogin" method="post">
    <p>账户:<input type="text" name="account"></p>
    <p>密码:<input type="password" name="password"></p>
    <p>回调地址<input type="text" name="redirect_uri" value="${redirect_uri}" readonly></p>
    <p>申请公司:${company_info.clientName}</p>
    <p>申请公司标识:${company_info.clientId}</p>
    <p>
        <button>登录</button>
    </p>
</form>
</body>
</html>

error.ftl

<!doctype html>
<html lang="zh">
<head>
    <title>Document</title>
</head>
<body>
<h1>请检查贵公司是否与我们有合作</h1>
<h1>也可能你拒绝了授权</h1>
</body>
</html>

auth.ftl

<!doctype html>
<html lang="zh">
<head>
    <title>Document</title>
</head>
<body>
<form action="${springMacroRequestContext.contextPath}/auth/doAuth">
    <p><input type="text" name="snowId" value="${snowId}" readonly></p>
    <button>同意授权</button>
</form>
</body>
</html>

4. 授权处理

在 wx-server 的项目中,创建一个JwtUtils。

JwtUtils

package com.wx.server.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Data
public class JwtUtils {

    private String secret = "wx1314";
    private Long expiration = Integer.valueOf(1000 * 60 * 60).longValue();

    /**
     * 从数据生成令牌
     */
    private String generateToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + expiration);
        return
                Jwts
                        .builder()
                        .setClaims(claims)
                        .setExpiration(expirationDate)
                        .signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    /**
     * 获取令牌中的数据
     */
    public Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 生成令牌(携带了用户名与签发时间)
     */
    public String generateToken(String openId) {
        Map<String, Object> claims = new HashMap<>(2);
        claims.put(Claims.SUBJECT, openId);
        claims.put(Claims.ISSUED_AT, new Date());
        return generateToken(claims);
    }


    /**
     * 获取令牌中的openId
     */
    public String getOpenIdFromToken(String token) {
        long id;
        try {
            Claims claims = getClaimsFromToken(token);
            return claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 判断令牌是否过期
     public Boolean isTokenExpired(String token) {
     Claims claims = getClaimsFromToken(token);
     Date expiration = claims.getExpiration();
     return expiration.before(new Date());
     }
     */

    /**
     * 刷新令牌
     public String refreshToken(String token) {
     String refreshedToken;
     try {
     Claims claims = getClaimsFromToken(token);
     claims.put(Claims.ISSUED_AT, new Date());
     refreshedToken = generateToken(claims);
     } catch (Exception e) {
     refreshedToken = null;
     }
     return refreshedToken;
     }
     */

    /**
     * 验证令牌
     public Boolean validateToken(String token, UserDetails userDetails) {
     User user = (User) userDetails;
     String username = getUsernameFromToken(token);
     return !isTokenExpired(token) && username.equals(user.getUsername());
     }
     */

}

在 wx-server 的项目中,创建一个AuthController,处理登入和获得客户端的信息及回应处理转发。

AuthController

package com.wx.server.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.yitter.idgen.YitIdHelper;
import com.wx.server.pojo.Company;
import com.wx.server.pojo.User;
import com.wx.server.service.ICompanyService;
import com.wx.server.service.IUserService;
import com.wx.server.util.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
import org.apache.oltu.oauth2.as.request.OAuthTokenRequest;
import org.apache.oltu.oauth2.as.response.OAuthASResponse;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.apache.oltu.oauth2.common.message.types.ParameterStyle;
import org.apache.oltu.oauth2.rs.request.OAuthAccessResourceRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Controller
@RequestMapping("/auth")
@Slf4j
@SuppressWarnings("all")
public class AuthController {

    @Autowired
    private ICompanyService companyService;
    @Autowired
    private IUserService userService;
    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("/code")
    public String sendCode(HttpServletRequest request, Model model) {
        try {
            //解析请求
            OAuthAuthzRequest oathReq = new OAuthAuthzRequest(request);
            //获取到客户端的id
            String clientId = oathReq.getClientId();
            if (clientId == null) return "redirect:/error";
            Company company = companyService.getOne(new QueryWrapper<Company>().lambda().eq(Company::getClientId, clientId), false);
            //如果客户端跟我们没有合作
            if (company == null) return "redirect:/error";
            // 将oathReq.getRedirectURI()的值赋值给model中的redirect_uri
            model.addAttribute("redirect_uri", oathReq.getRedirectURI());
            // 将company的值赋值给model中的company_info
            model.addAttribute("company_info", company);
        } catch (Exception e) {
            return "error";
        }
        //跳到认证登录页
        return "login";
    }

    @PostMapping("/doLogin")
    public String doLogin(User user, String redirect_uri, Model model) {
        //用户登录
        User one = userService.getOne(new QueryWrapper<User>().lambda()
                .eq(User::getAccount, user.getAccount())
                .eq(User::getPassword, user.getPassword()));
        //登录失败
        if (one == null) {return "redirect:/error";}
        //生成一个雪花id
        long snowId = YitIdHelper.nextId();
        //将需要的数据放到缓存中
        //1.用作等会需要的判断,判断用户进行授权的请求是否来源与后端
        //2.将用户的数据放入到缓存中,避免在前端暴露
        redisTemplate.opsForValue().set("request:" + snowId, redirect_uri, 5, TimeUnit.MINUTES);
        //将one.getId()存储到redis中,以snowId为key
        redisTemplate.opsForValue().set("user:" + snowId, one.getId());
        //将snowId存储到model中
        model.addAttribute("snowId", snowId + "");
        //返回auth页面
        return "auth";
    }

    @GetMapping("/doAuth")
    public String doAuth(HttpServletRequest request, String snowId) throws Exception {
        //回调路径
        Object obj = redisTemplate.opsForValue().get("request:" + snowId);
        if (obj == null) {
            return "redirect:/error";
        }
        //request:123456
        String userId = redisTemplate.opsForValue().get("user:" + snowId).toString();
        String code = getCode();
        redisTemplate.opsForValue().set("code:" + code, userId, 5, TimeUnit.MINUTES);
        //获取构建响应的对象
        OAuthASResponse.OAuthAuthorizationResponseBuilder builder =
                OAuthASResponse.authorizationResponse(request, HttpServletResponse.SC_OK);
        builder.setCode(code);
        String redirectURI = obj.toString();
        OAuthResponse oauthResp = builder.location(redirectURI).buildQueryMessage();
        //形成路径路径拼接效果 http://localhost:80/client/callback?code=xx
        String uri = oauthResp.getLocationUri();
        // redirect:/client/callback?code=xx
        return "redirect:" + uri;
    }

    /**
     * 生成授权码方法
     */
    public String getCode() {
        Random r = new Random();
        String code = "";
        for (int i = 0; i < 8; ++i) {
            int temp = r.nextInt(52);
            char x = (char) (temp < 26 ? temp + 97 : (temp % 26) + 65);
            code += x;
        }
        return code;
    }

    @PostMapping("/token")
    public HttpEntity getAccessToken(HttpServletRequest request) throws OAuthProblemException, OAuthSystemException {
        //OAuthTokenRequest解析请求
        OAuthTokenRequest tokenReq = new OAuthTokenRequest(request);
        //获得客户端的信息
        String clientId = tokenReq.getClientId();
        String clientSecret = tokenReq.getClientSecret();
        Company company = companyService.getOne(new QueryWrapper<Company>().lambda().eq(Company::getClientId, clientId).eq(Company::getClientSecret, clientSecret), false);
        //去数据库做查询
        if (company != null) {
            //做授权码的判断
            String code = tokenReq.getCode();
            //将授权码带入到缓存中查看是否有对应的数据
            String userId = redisTemplate.opsForValue().get("code:" + code).toString();
            if(userId==null){
                System.out.println("授权码可能过期或者伪造了");
                return null;
            }
            String openId = userService.getById(userId).getOpenId();
            String token = new JwtUtils().generateToken(openId);
            //构造保护令牌的响应对象
            OAuthResponse oAuthResponse = OAuthASResponse
                    .tokenResponse(HttpServletResponse.SC_OK)
                    .setAccessToken(token)
                    .buildJSONMessage();
            return new ResponseEntity(oAuthResponse.getBody(), HttpStatus.valueOf(oAuthResponse.getResponseStatus()));
        }
        return null;
    }

    @GetMapping("/userinfo")
    @ResponseBody
    public Object getUserInfo(HttpServletRequest request) throws OAuthProblemException, OAuthSystemException {
        //OAuthAccessResourceRequest解析请求
        OAuthAccessResourceRequest oAuthAccessResourceRequest = new OAuthAccessResourceRequest(request, ParameterStyle.HEADER);
        //请求头中获取令牌
        String token = oAuthAccessResourceRequest.getAccessToken();
        //判断令牌是否是我发给你的
        String openId = new JwtUtils().getOpenIdFromToken(token);
        if (openId == null) {
            return null;
        }
        User user = userService.getOne(new QueryWrapper<User>().lambda().eq(User::getOpenId, openId));
        return user;
    }

}

在 zking-server 的项目中,创建一个GetAuthorizationController,服务器授权的跳转及授权码的回调

GetAuthorizationController

package com.zking.server.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zking.server.pojo.User;
import com.zking.server.pojo.ZkingUser;
import com.zking.server.service.IZkingUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.oltu.oauth2.client.OAuthClient;
import org.apache.oltu.oauth2.client.URLConnectionClient;
import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest;
import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse;
import org.apache.oltu.oauth2.client.response.OAuthResourceResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

@Controller
@RequestMapping("/client")
@SuppressWarnings("all")
@Slf4j
public class GetAuthorizationController {

    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private IZkingUserService zkingUserService;

    private static String CLIENT_ID = "123";
    private static String CLIENT_SECRET = "123";

    @GetMapping("/login")
    public String login() {
        return "login";
    }

    /**
     * 跳往微信服务器页面
     */
    @GetMapping("/wx")
    public String getCode() throws OAuthSystemException {
        OAuthClientRequest oAuthClientRequest = OAuthClientRequest
                .authorizationLocation("code")
                .setClientId("ai")
                .setRedirectURI("http://localhost:8080/client/callbackCode")
                .setResponseType("code")
                .buildQueryMessage();
        String uriString = oAuthClientRequest.getLocationUri();
        //重定向到资源所有者,获取验证码
        return "redirect:http://localhost:9999/auth/" + uriString;
    }

    /**
     * 授权码回调方法
     */
    @RequestMapping("/callbackCode")
    public String callbackCode(HttpServletRequest request) throws Exception {
        //获得授权码
        String code = request.getParameter("code");
        //根据授权码获得令牌
        //OAuthClientRequest封装了所有的参数
        //OAuthClient发起请求
        OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
        OAuthClientRequest tokenRequest = OAuthClientRequest
                .tokenLocation("http://localhost:9999/auth/token")
                .setClientId(CLIENT_ID)
                .setClientSecret(CLIENT_SECRET)
                .setGrantType(GrantType.AUTHORIZATION_CODE)
                .setCode(code)
                .setRedirectURI("http://localhost:8080/client/callbackCode")
                .buildQueryMessage();
        //通过Code,向认证服务器申请令牌
        OAuthJSONAccessTokenResponse tokenResp = oAuthClient.accessToken(tokenRequest, OAuth.HttpMethod.POST);
        //获取令牌 21713781312371token
        String accessToken = tokenResp.getAccessToken();
        OAuthClientRequest userInfoRequest =
                new OAuthBearerClientRequest("http://localhost:9999/auth/userinfo")
                        .setAccessToken(accessToken)
                        .buildHeaderMessage();
        OAuthResourceResponse resourceResponse = oAuthClient.resource(userInfoRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class);
        String json = resourceResponse.getBody();
        User user = objectMapper.readValue(json, User.class);
        //获得用户的openId
        String openId = user.getOpenId();
        //判断我数据库是否有这个人
        ZkingUser one = zkingUserService.getOne(new QueryWrapper<ZkingUser>().lambda().eq(ZkingUser::getOpenId, openId), false);
        //判断
        if(one==null){
            return  "phone";
        }
        return "index";
    }

    @GetMapping("/bind")
    @ResponseBody
    public String bind(ZkingUser user) {
        zkingUserService.save(user);
        return "yes";
    }

}

每篇一获

在项目中使用授权码模式可以带来以下收获:

1. 安全性:授权码模式通过将访问令牌的获取过程分为两步,有效减少了访问令牌在传输过程中被窃取的风险。用户的凭证信息不会直接暴露给客户端,提高了安全性。

2. 用户体验:授权码模式可以让用户在授权服务器上进行登录和授权,而不是直接在客户端中输入凭证信息。这种方式可以提供更好的用户体验,同时也可以减少客户端的安全风险。

3. 灵活性:授权码模式可以适用于多种类型的应用程序,包括Web应用程序、移动应用程序和第三方应用程序。这种灵活性可以使得授权码模式成为一个通用的授权方式,适用于各种不同场景的项目。

4. 标准化:授权码模式是OAuth 2.0标准中定义的一种授权方式,使用它可以使得项目符合OAuth 2.0的标准规范,提高了项目的可维护性和可扩展性。

总之,使用授权码模式可以提高项目的安全性和用户体验,同时也可以使得项目符合标准规范,具有更好的灵活性和通用性。

文章来源:https://blog.csdn.net/SAME_LOVE/article/details/135260902
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。