1.使用场景:

在目前大多数第三方登录的都是使用oauth2的授权码模式实现登录验证,避免用户再次注册登录。

授权码模式的认证过程主要是:

1、用户客户端请求认证服务器的认证接口,并附上回调地址;

2、认证服务接口接收到认证请求后调整到自身的登录界面;

3、用户输入用户名和密码,点击确认,跳转到授权、拒绝提示页面(也可省略);

4、用户点击授权或者默认授权后,跳转到微服务客户端的回调地址,并传入参数 code;

5、回调地址一般是一个 RESTful 接口,此接口拿到 code 参数后,再次请求认证服务器的 token 获取接口,用来换取 access_token 等信息;

6、获取到 access_token 后,拿着 token 去请求各个微服务客户端的接口。

注意上面所说的用户客户端可以理解为浏览器、app 端,微服务客户端就是我们系统中的例如订单服务、用户服务等微服务,认证服务端就是用来做认证授权的服务,相对于认证服务端来说,各个业务微服务也可以称作是它的客户端。

2.代码实现

在​​#yyds干货盘点# Spring cloud Oauth2增强JWT实现登录验证授权​​基础代码上进行修改

2.1 oauth2-service修改

修改配置

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

/**
* 允许匿名访问所有接口 主要是 oauth 接口
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.and()
.authorizeRequests()
.antMatchers("/**").permitAll();
}
}

2.2 oauth2-client修改

1.添加依赖,引入 okhttp 和 thymeleaf 是因为要做一个简单的页面并模拟正常的认证过程。

<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2.修改yml, 添加了redirect-url属性,用于请求接口时调用

spring:
application:
name: oauth2-client
redis:
database: 2
host: localhost
port: 6379
password: 123456
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
timeout: 100ms
server:
port: 8002
servlet:
context-path: /oauth2-client


security:
oauth2:
client:
client-id: oauth2-client
client-secret: oauth2-secret-8888
user-authorization-uri: http://localhost:8001/oauth/authorize
access-token-uri: http://localhost:8001/oauth/token
resource:
jwt:
key-uri: http://localhost:8001/oauth/token_key
key-value: dev
authorization:
check-token-access: http://localhost:8001/oauth/check_token
redirect-url: http://localhost:8002/oauth2-client/login

3.修改配置

使用 jwt 作为 token 的存储,注意允许/login 接口无授权访问,这个地址是认证的回调地址,会返回 code 参数,code用于完成授权验证操作。

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {


@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();

accessTokenConverter.setSigningKey("dev");
accessTokenConverter.setVerifierKey("dev");
return accessTokenConverter;
}

@Autowired
private TokenStore jwtTokenStore;

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(jwtTokenStore);
}

@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login").permitAll();
}
}

4.控制层请求完成登录授权验证

@Slf4j
@Controller
public class CodeClientController {

@Value("${security.oauth2.client.access-token-uri}")
private String tokenUrl;

@Value("${security.oauth2.redirect-url}")
private String redirectUrl;

@Value("${security.oauth2.client.client-id}")
private String clientId;

@Value("${security.oauth2.client.client-secret}")
private String clientSecret;

@Autowired
private Base64Util base64Util;

/**
* 用来展示index.html 模板
* @return
*/
@GetMapping(value = "index")
public String index(){
return "index";
}

@GetMapping(value = "login")
public Object login(String code,Model model) {
OkHttpClient httpClient = new OkHttpClient();
RequestBody body = new FormBody.Builder()
.add("grant_type", "authorization_code")
.add("client", clientId)
.add("redirect_uri",redirectUrl)
.add("code", code)
.build();
String str = clientId + ":" + clientSecret;
String authorization = base64Util.encode(str);
Request request = new Request.Builder()
.url(tokenUrl)
.post(body)
.addHeader("Authorization", "Basic " + authorization)
.build();
try {
Response response = httpClient.newCall(request).execute();
String result = response.body().string();
ObjectMapper objectMapper = new ObjectMapper();
Map tokenMap = objectMapper.readValue(result,Map.class);
String accessToken = tokenMap.get("access_token").toString();
Claims claims = Jwts.parser()
.setSigningKey("dev".getBytes(StandardCharsets.UTF_8))
.parseClaimsJws(accessToken)
.getBody();
String userName = claims.get("user_name").toString();
model.addAttribute("username", userName);
model.addAttribute("accessToken", result);
return "index";
} catch (Exception e) {
e.printStackTrace();
}
return null;
}


}

5.base64加解密工具类

@Component
public class Base64Util {

public static void main(String[] args) throws Exception {
String str = "oauth2-client:oauth2-secret-8888";

base64(str);

enAndDeCode(str);

}

public String encode(String str) {
byte[] bytes = str.getBytes();

//Base64 加密
String encoded = Base64.getEncoder().encodeToString(bytes);
System.out.println("Base 64 加密后:" + encoded);
return encoded;
}

/**
* Base64
*
*/
public static void base64(String str) {
byte[] bytes = str.getBytes();

//Base64 加密
String encoded = Base64.getEncoder().encodeToString(bytes);
System.out.println("Base 64 加密后:" + encoded);

//Base64 解密
byte[] decoded = Base64.getDecoder().decode(encoded);

String decodeStr = new String(decoded);
System.out.println("Base 64 解密后:" + decodeStr);

System.out.println();

}

/**
* BASE64加密解密
*/
public static void enAndDeCode(String str) throws Exception {
String data = encryptBASE64(str.getBytes());
System.out.println("sun.misc.BASE64 加密后:" + data);

byte[] byteArray = decryptBASE64(data);
System.out.println("sun.misc.BASE64 解密后:" + new String(byteArray));
}

/**
* BASE64解密
* @throws Exception
*/
public static byte[] decryptBASE64(String key) throws Exception {
return (new BASE64Decoder()).decodeBuffer(key);
}

/**
* BASE64加密
*/
public static String encryptBASE64(byte[] key) throws Exception {
return (new BASE64Encoder()).encodeBuffer(key);
}


}

6.授权完成后跳转前端页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>测试主页</title>


</head>
<body>
<div>
<a href="http://localhost:8001/oauth/authorize?client_id=oauth2-client&response_type=code&redirect_uri=http://localhost:8002/oauth2-client/login">登录</a>
<span th:text="'当前认证用户:' + ${username}"></span>
<span th:text="${accessToken}"></span>
</div>
</body>
</html>

3.数据库插入数据

INSERT INTO `spring_cloud`.`oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('oauth2-client', NULL, '$2a$10$p4oERvFiyLCvwq7t3y8E0eJsq5D1JwFqP.slVc1xB6yXy/V23KNQu', 'all', 'authorization_code,refresh_token,password', 'http://localhost:8002/oauth2-client/login', NULL, 3600, 36000, NULL, '1');
INSERT INTO `spring_cloud`.`user_info`(`id`, `username`, `password`) VALUES ('1', 'admin', '$2a$10$erpn.mfZuRrHpFxqiQrD1.u02HMoaoCJ9.2d7azW42pmt/w7b3Fti');

4.运行oauth2-service, oauth2-client项目后,打开浏览器,输入地址

​http://localhost:8001/oauth/authorize?client_id=oauth2-client&response_type=code&redirect_uri=http://localhost:8002/oauth2-client/login​

#yyds干货盘点# Spring cloud Oauth2使用授权码模式实现登录验证授权_授权码模式

由于是配置读取数据库用户信息的,插入了用户信息为admin, 密码123456,输入后登录进入页面。

#yyds干货盘点# Spring cloud Oauth2使用授权码模式实现登录验证授权_授权码模式_02

来到授权确认页面,页面上有 Authorize 和 Deny (授权和拒绝)两个按钮。可通过将 autoapprove 字段设置为 0 来取消此页面的展示,默认直接同意授权。点击同意授权后,跳转到了回调地址,完成了登录验证授权。

#yyds干货盘点# Spring cloud Oauth2使用授权码模式实现登录验证授权_oauth2_03