目录
背景
公司做平台产品,很早之前就想着要做类似于微信,QQ那种的授权登陆系统,用来统一授权子系统。于是在网上搜罗很多大神的讲解后,根据自己想法写出一套完整的oauth授权登陆系统
Oauth2.0简介
oauth2.0是OAuth协议的延续版本,是一个互联网标准的用户验证和授权协议,通俗讲就是前人想出的一套用于用户验证和授权的方案,其大致流程如下:
(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。
同时在客户端规定了四种授权模式:授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)、客户端模式(client credentials)。本次介绍内容使用了授权码模式,也是功能最完整、流程最严密的授权模式。
时序图设计
此流程与网上众多关于oauth登陆的设计大同小异,唯一需要特殊说明的就是多设立了一个loginServer服务,此处目的是将授权系统中的用户信息校验解耦,使此系统可灵活接入其他平台的现有账户体系进行使用。
具体实现
一、oauthServer服务
1、建立一个空的maven项目,具体方式不再赘述,根据自己IDE或喜好创建就好
2、引入相关jar包,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>
<groupId>com.colin</groupId>
<artifactId>oauthServer</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<!-- jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
</dependencies>
</project>
3、配置springboot配置文件application.properties,具体如下:
# 配置当前服务端口
server.port=1111
# 配置thymeleaf
spring.thymeleaf.prefix=classpath:/views/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false
# redis配置
spring.redis.host=*
spring.redis.port=*
spring.redis.password=*
spring.redis.pool.max-active=100
spring.redis.pool.max-wait=3000
spring.redis.pool.max-idle=20
spring.redis.pool.min-idle=5
# jdbc配置
spring.datasource.url=*
spring.datasource.username=*
spring.datasource.password=*
# mybatis配置
mybatis.type-aliases-package=com.colin
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.com.example=debug
# 开启此配置使我们可以自定义扩展springBoot默认的bean
spring.main.allow-bean-definition-overriding=true
4、配置启动类
package com.colin.oauth;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
@SpringBootApplication
@EnableAuthorizationServer//oauth认证服务
@MapperScan("com.colin.mapper")//指定mybatis的mapper路径,此次几乎无用
public class OauthServer {
public static void main(String[] args) {
SpringApplication.run(OauthServer.class, args);
}
}
5、配置security
package com.colin.oauth.config;
import com.colin.oauth.service.UserService;
import javax.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Resource
private UserService userService;
/**
* 指定一种加密方式
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 配置基本用户信息及加密方式
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
/**
* 配置了一个表单登陆
* 访问此服务时会需要先进行登陆
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
//关闭打开的csrf保护
csrf().disable().
//配置一个表单登陆
formLogin().
//自定义登陆页面地址
loginPage("/auth/login")
;
}
}
此类中的UserService是自定义的用户信息获取接口服务,如下:
package com.colin.oauth.service;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* 继承security中的service接口
* 用来自定义当前系统用户信息获取
*/
public interface UserService extends UserDetailsService{
}
6、配置authorization
package com.colin.oauth.config;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
@Configuration
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter{
@Resource
private DataSource dataSource;
@Resource
private PasswordEncoder passwordEncoder;
/**
* 生成的 Token 要往哪里存储,
* 我们可以存在 Redis中,也可以存在内存中,
* 也可以结合 JWT 等等,
* 最终使用jdbc存储,数据可读性更强些
* 存储表:oauth_access_token,oauth_refresh_token
*/
@Bean
public TokenStore tokenStore(){
return new JdbcTokenStore(dataSource);
}
/**
* 配置授权码的存储
* 由于一次性,暂放在当前内存中
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices(){
return new InMemoryAuthorizationCodeServices();
}
/**
* 授权信息保存策略
* 当前保存至JDBC
* 存储表:oauth_approvals
*/
@Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(dataSource);
}
/**
* 覆盖默认ClientDetailsService的实现
* 将客户端校验信息存入数据库
* 存储表:oauth_client_details
*/
@Bean
public JdbcClientDetailsService jdbcClientDetailsService(){
JdbcClientDetailsService detailsService = new JdbcClientDetailsService(dataSource);
detailsService.setPasswordEncoder(passwordEncoder);
return detailsService;
}
/**
* 配置令牌的存储与认证
*/
@Bean
public AuthorizationServerTokenServices authorizationServerTokenServices(){
DefaultTokenServices services = new DefaultTokenServices();
services.setClientDetailsService(jdbcClientDetailsService());
services.setSupportRefreshToken(true);
services.setTokenStore(tokenStore());
//设置access_token失效时间
services.setAccessTokenValiditySeconds(60*60*2);
//设置refresh_token失效时间
services.setRefreshTokenValiditySeconds(60*60*3);
return services;
}
/**
* 用来配置令牌端点的安全约束,也就是这个端点谁能访问,谁不能访问
* checkTokenAccess 是指一个 Token 校验的端点,
* 这个端点我们设置为可以直接访问
* 在后面,当资源服务器收到 Token 之后,需要去校验Token的合法性,就会访问这个端点
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients();
}
/**
* 用来配置客户端的详细信息
* 配置校验客户端
* 这里我们分别配置了客户端的 id,secret、资源 id、授权类型、授权范围以及重定向 uri
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//改为jdbc模式
clients.withClientDetails(jdbcClientDetailsService());
}
/**
* 配置令牌的访问端点和令牌服务
* OAuth2的主配置信息
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.approvalStore(approvalStore())
.authorizationCodeServices(authorizationCodeServices())
.tokenServices(authorizationServerTokenServices());
//自定义授权页面配置
endpoints.pathMapping("/oauth/confirm_access","/custom/confirm_access");
}
}
7、编写自定义UserService的实现UserServiceImpl
package com.colin.oauth.service.impl;
import com.colin.oauth.entity.Grant;
import com.colin.oauth.entity.SysUser;
import com.colin.oauth.enums.GrantEnum;
import com.colin.oauth.service.UserService;
import com.colin.oauth.util.SysSecurityUtil;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
@Resource
private PasswordEncoder passwordEncoder;
/**
* 为方便校验,此处写死待校验用户
*/
private static HashSet<String> USERS = new HashSet<>();
static {
USERS.add("colin1");
USERS.add("colin2");
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//可从其他服务(如loginServer底层服务)获取用户信息进行校验
//也可以从本地数据库读取进行验证
if(!USERS.contains(s)){
throw new UsernameNotFoundException("用户不存在");
}
SysUser user = new SysUser();
user.setUsername(s);
//此处SysSecurityUtil定义了一套前后一致的加密规则,省去密码再次读取
//也可直接从其他服务或本地数据库获取密码信息,再使用passwordEncoder进行编码
user.setPassword(passwordEncoder.encode(SysSecurityUtil.generatePwd(s)));
Grant grant = new Grant();
grant.setAuthorities(GrantEnum.USER.name());
List<Grant> list = new ArrayList<>();
list.add(grant);
user.setGrants(list);
return user;
}
}
8、涉及的实体及枚举如下
package com.colin.oauth.entity;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
/**
* 实现security封装的UserDetails进行扩展,
* 便于自定义用户信息操作
*/
public class SysUser implements UserDetails{
/**
* 用户名
*/
private String username;
/**
* 用户密码
*/
private String password;
/**
* 用户权限
*/
private List<Grant> grants;
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setGrants(List<Grant> grants) {
this.grants = grants;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grants;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
package com.colin.oauth.entity;
import org.springframework.security.core.GrantedAuthority;
/**
* 实现security封装的GrantedAuthority权限对象进行扩展,
* 便于自定义用户权限操作
*/
public class Grant implements GrantedAuthority {
//用户权限
private String authorities;
@Override
public String getAuthority() {
return authorities;
}
public void setAuthorities(String authorities) {
this.authorities = authorities;
}
}
package com.colin.oauth.enums;
public enum GrantEnum {
/**
* 管理员权限
*/
ADMIN,
/**
* 普通用户权限
*/
USER
}
9、自定义页面路由
此处写了2个controller,一个用来自定义登陆路由处理,一个自定义授权路由处理
package com.colin.oauth.controller;
import com.colin.oauth.util.SysSecurityUtil;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
/**
* 路由到第三方页面
*/
@GetMapping("/auth/login")
public void loginPage(Model model, HttpServletResponse response){
try {
response.sendRedirect("http://192.168.1.227:1114/login/page");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 第三方登陆成功回调此接口
*/
@GetMapping("/oauthServer/login")
public String oauthLogin(String u,Model model){
model.addAttribute("username", u);
//此处为了方便,直接加密生成密码,与UserService中的验证相呼应
//后续可以考虑不再设置
model.addAttribute("pwd", SysSecurityUtil.generatePwd(u));
return "login";
}
}
package com.colin.oauth.controller;
import java.util.Map;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
/**
* 由oauth封装时将认证信息放置在session中,此处需要@SessionAttributes
* 以便获取认证信息
*/
@Controller
@SessionAttributes("authorizationRequest")
public class GrantController {
@RequestMapping("/custom/confirm_access")
public String grantPage(Map<String, Object> model, Model viewModel){
AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");
viewModel.addAttribute("clientId", authorizationRequest.getClientId());
return "grant";
}
}
10、自定义登陆授权页面
根据application.properties中的配置,将html文件放置在目录/resources/views/下
本次用到的两个html页面大致代码如下:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<style>
.login-container {
margin: 50px;
width: 100%;
}
.form-container {
margin: 0px auto;
width: 50%;
text-align: center;
box-shadow: 1px 1px 10px #888888;
height: 300px;
padding: 5px;
}
input {
margin-top: 10px;
width: 350px;
height: 30px;
border-radius: 3px;
border: 1px #E9686B solid;
padding-left: 2px;
}
.btn {
width: 350px;
height: 35px;
line-height: 35px;
cursor: pointer;
margin-top: 20px;
border-radius: 3px;
background-color: #E9686B;
color: white;
border: none;
font-size: 15px;
}
.title{
margin-top: 5px;
font-size: 18px;
color: #E9686B;
}
</style>
<body>
<div class="login-container" style="display: none">
<div class="form-container">
<p class="title">用户登录</p>
<form id="loginForm" method="post" action="/auth/login">
<input type="text" name="username" th:value="${username}" placeholder="用户名"/>
<br>
<input type="password" name="password" th:value="${pwd}" placeholder="密码"/>
<br>
<button type="submit" class="btn">登 录</button>
</form>
<p style="color: red" th:if="${param.error}">用户名或密码错误</p>
</div>
</div>
<script>
var loginForm = document.getElementById("loginForm");
loginForm.submit();
</script>
</body>
</html>
注意:此登陆页面只作为一个与spring-security衔接的跳转使用,实际的登陆逻辑实现由loginServer服务提供
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>授权</title>
</head>
<style>
body{
margin:0;
padding:0;
background-color: white;
}
html{
padding: 0;
margin: 0;
}
.title {
background-color: #e90908;
height: 50px;
padding-left: 20%;
padding-right: 20%;
color: white;
line-height: 50px;
font-size: 18px;
text-align: center;
}
.container{
clear: both;
text-align: center;
}
.btn {
width: 350px;
height: 35px;
line-height: 35px;
cursor: pointer;
margin-top: 20px;
border-radius: 3px;
background-color: #e90908;
color: white;
border: none;
font-size: 15px;
}
</style>
<body style="margin: 0">
<div class="title">自定义授权页面</div>
<div class="container">
<h3>该应用将获取你的以下信息</h3>
<p>昵称,头像和性别</p>
<form method="post" action="/oauth/authorize">
<input type="hidden" name="user_oauth_approval" value="true">
<input type="hidden" name="scope.all" value="true"/>
<button class="btn" type="submit"> 同意</button>
</form>
</div>
</body>
</html>
二、loginServer服务
1、建立一个空的maven项目
2、引入jar,此处只是一个简单的demo项目,只为完成流程的通顺,所以之引入了springboot的web基础包,以及页面使用的thymeleaf包。实际项目中需要根据自己的需求进行开发,对应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>
<groupId>com.colin</groupId>
<artifactId>loginServer</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
</project>
3、配置springboot配置文件application.properties
# 配置当前服务端口
server.port=1112
# 配置thymeleaf
spring.thymeleaf.prefix=classpath:/views/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false
4、配置启动类
package com.colin.login;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LoginServer {
public static void main(String[] args) {
SpringApplication.run(LoginServer.class, args);
}
}
5、登陆接口
package com.colin.login.controller;
import java.util.HashSet;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class LoginController {
/**
* 与oauthServer中用户校验一致,此处写死待校验用户
* 实际项目中需要根据自身业务处理
*/
private static HashSet<String> USERS = new HashSet<>();
static {
USERS.add("colin1");
USERS.add("colin2");
}
/**
* 跳转登陆页面
*/
@GetMapping("/login/page")
public String loginPage(Model model){
return "login";
}
/**
* 模拟登陆
*/
@PostMapping("/login")
public String toLogin(Model model,String username, String password, HttpServletResponse response){
try {
//模拟登陆逻辑
if(!USERS.contains(username)){
model.addAttribute("error", "用户不存在");
return "login";
}
if(!"1".equals(password)){
model.addAttribute("error", "用户名密码错误");
return "login";
}
//登陆成功,返回oauthServer并携带登陆成功后的用户信息
response.sendRedirect("http://192.168.1.227:1111/oauthServer/login?u="+username);
} catch (Exception e) {
e.printStackTrace();
}
return "login";
}
}
6、登陆页面
根据配置,h5页面路径为:/resources/views/
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<style>
body{
margin:0;
padding:0;
background-color: white;
}
.login-container {
top: 10px;
margin: 0;
width: 100%;
background-color: white;
}
.form-container {
margin: 0 auto;
width: 50%;
text-align: center;
box-shadow: 1px 1px 10px #888888;
height: 300px;
padding: 5px;
background-color: azure;
}
input {
margin-top: 10px;
width: 350px;
height: 30px;
border-radius: 3px;
border: 1px #e90908 solid;
padding-left: 2px;
}
.btn {
width: 350px;
height: 35px;
line-height: 35px;
cursor: pointer;
margin-top: 20px;
border-radius: 3px;
background-color: #e90908;
color: white;
border: none;
font-size: 15px;
}
.title{
margin-top: 5px;
font-size: 18px;
color: #e90908;
}
</style>
<body>
<div class="login-container">
<div class="form-container">
<p class="title">模拟第三方登陆页面</p>
<form name="loginForm" method="post" action="/login">
<input type="text" name="username" placeholder="用户名"/>
<br>
<input type="password" name="password" placeholder="密码"/>
<br>
<button type="submit" class="btn">登 录</button>
</form>
<p style="color: red" th:if="${error}" th:text="${error}"></p>
</div>
</div>
</body>
</html>
三、resourceServer服务
1、创建空的maven项目
2、引入jar包,同样,此处只为整体流程跑通,设置了简单的接口,正式项目需根据自己需求进行扩展
<?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>
<groupId>com.colin</groupId>
<artifactId>resourceServer</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
</dependencies>
</project>
3、配置springboot配置文件application.properties
server.port=1113
4、配置启动类
package com.colin.resource;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
@SpringBootApplication
@EnableResourceServer//资源服务
public class ResourceServer {
public static void main(String[] args) {
SpringApplication.run(ResourceServer.class, args);
}
}
5、配置服务访问安全策略
package com.colin.resource.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
@Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
/**
* 配置了 access_token 的校验地址、
* client_id、
* client_secret
* 这三个信息,
* 当用户来资源服务器请求资源时,会携带上一个 access_token,
* 通过这里的配置,就能够校验出 token 是否正确等
*/
@Bean
public RemoteTokenServices tokenServices(){
RemoteTokenServices services = new RemoteTokenServices();
services.setCheckTokenEndpointUrl("http://192.168.1.227:1111/oauth/check_token");
//经测试,此处传递的client信息只要在oauth_client_details中存在即可
//后续可设计为本系统主账号信息做统一校验
services.setClientId("javaboy");
services.setClientSecret("1");
return services;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("res1").tokenServices(tokenServices());
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//可直接访问的地址
.antMatchers("/access/**").permitAll()
//需要admin权限访问的地址
.antMatchers("/admin/**").hasRole("admin")
//其他访问都需要验证
.anyRequest().authenticated()
.and()
//使支持跨域
.cors()
;
}
}
6、模拟资源接口
package com.colin.resource.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HellowController {
/**
* 受限制访问,用户权限不限
*/
@GetMapping("/hello")
public String hello(){
return "hello";
}
/**
* 受限制访问,只允许admin权限用户访问
*/
@GetMapping("/admin/hello")
public String adminHello(){
return "adminHello";
}
/**
* 不受限制
*/
@GetMapping("/access/hello")
public String accessHello(){
return "accessHello";
}
}
四、cleint服务
此服务就是一个简单的页面去访问授权连接即可,为了方便跳转,另起一个端口作为页面服务来模拟第三方
1、创建一个空的maven项目
2、引入jar
<?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>
<groupId>com.colin</groupId>
<artifactId>client</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
3、配置文件
server.port=1114
4、启动类
package com.colin.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ClientServer {
public static void main(String[] args) {
SpringApplication.run(ClientServer.class, args);
}
}
5、模拟第三方页面,放置在/resource/static/下即可,直接访问测试
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>模拟第三方页面</title>
</head>
<body>
<div>
<a href="http://192.168.1.227:1111/oauth/authorize?client_id=colin0702&response_type=code&scope=all&redirect_uri=http://192.168.1.227:1114/index.html">
点击授权
</a>
</div>
</body>
</html>
client_id:第三方申请应用时生成,存放在oauth_client_details表中
response_type:授权模式,code即表示为授权码模式
scope:默认all即可
redirect_uri:授权之后回调页面地址,此地址需要也第三方申请应用时填写的地址一致,也存放在oauth_client_details表中
至此,简单的一套oauth2.0授权系统基本完成!
演示
1、分别启动上边写好的服务
2、插入一条第三方申请信息,对于相关表的创建sql会在最后整理出来
/**测试数据*/
INSERT INTO `oauth`.`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 ('colin0702', 'res1', '$2a$10$dUD2wWPtFpHfI3TpAfdFBuL0TOpztgvz2xmOMip4ZNn2EWrDh9xYq', 'all', 'authorization_code,refresh_token', 'http://192.168.1.227:1114/index.html', NULL, NULL, NULL, NULL, NULL);
3、访问client服务的静态页面,模拟第三方页面
4、点击授权,访问oauthServer,之后会重新向至loginServer的登陆页面,如下:
5、输入预设的用户名密码,在loginServer服务登陆成功后,重定向至oauthServer服务跳转至授权页面
6、点击同意,oauthServer服务根据设定的回调地址,返回H5页面,并携带code信息
7、利用postman,模拟后台请求oauthServer服务,获取access_token信息
8、使用postman,模拟第三方访问资源服务resourceServer
对于admin权限的访问尝试
以上就是整套oauth2.0系统的实现尝试,利用了spring-cloud-security及spring-cloud-starter-oauth2中的成熟API及扩展,有想要查看全部代码及相关建表语句的可前往https://github.com/colinguangyi/oauth
另:未避免github访问延迟,将项目转至https://gitee.com/zhaolz/oauth
对于文中有不妥之处的,还望各位大神指点~