目录
一、引言
1. 详解
授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
适用场景 :
1. Web应用程序:当客户端是一个运行在Web服务器上的应用程序时,授权码模式是一个常见的选择。用户在Web应用程序中进行登录和授权,然后客户端使用授权码来获取访问令牌。
2. 移动应用程序:对于需要访问受保护资源的移动应用程序,授权码模式也是一个很好的选择。用户可以在移动应用程序中进行登录和授权,然后客户端使用授权码来获取访问令牌。
3. 第三方应用程序:当一个第三方应用程序需要代表用户访问受保护资源时,授权码模式也可以使用。用户可以在授权服务器上进行登录和授权,然后第三方应用程序使用授权码来获取访问令牌。
总之,授权码模式适用于需要在用户和客户端之间建立信任关系,并且需要通过授权码来获取访问令牌的场景。它是一种相对安全的授权方式,适用于多种不同类型的应用程序。
2. 实现流程
主要流程 :
-
用户访问客户端,客户端将用户重定向到授权服务器。
-
用户在授权服务器上登录并同意授权客户端请求的权限。
-
授权服务器将用户重定向回客户端,并附上一个授权码。
-
客户端收到授权码后,使用该授权码向授权服务器请求访问令牌。
-
客户端向授权服务器发送包含授权码的请求,并提供客户端凭证(客户端ID和客户端密钥)。
-
授权服务器验证客户端凭证和授权码,如果验证通过,授权服务器将颁发访问令牌给客户端。
-
客户端使用访问令牌向受保护资源服务器请求受保护资源。
通过以上流程,客户端可以通过授权码模式获取访问令牌,并使用该访问令牌访问受保护资源。这种方式可以保护用户的敏感信息,同时也可以保护客户端的凭证信息。
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,认证服务器接受请求之后的调转连接,可以根据这个连接将生成的授权码回传,必选项 |
scope | code发送给资源服务器申请的权限范围,可选项 |
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编写,自动生成直接使用
https://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的标准规范,提高了项目的可维护性和可扩展性。
总之,使用授权码模式可以提高项目的安全性和用户体验,同时也可以使得项目符合标准规范,具有更好的灵活性和通用性。