整理了依赖版本问题,完成OAuth2客户端单点登录功能

This commit is contained in:
wanggeng 2021-09-18 15:37:51 +08:00
parent f47bae8f9f
commit 9a2cb4bad3
28 changed files with 1359 additions and 521 deletions

View File

@ -14,24 +14,24 @@ import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "security.oauth2")
public class OAuth2ClientProperties {
private String oauth2Server;
private String oauth2Logout;
private String oauthServer;
private String oauthLogout;
private ClientProperties client;
public String getOauth2Server() {
return oauth2Server == null ? "" : oauth2Server.trim();
public String getOauthServer() {
return oauthServer == null ? "" : oauthServer.trim();
}
public void setOauth2Server(String oauth2Server) {
this.oauth2Server = oauth2Server;
public void setOauthServer(String oauthServer) {
this.oauthServer = oauthServer;
}
public String getOauth2Logout() {
return oauth2Logout == null ? "" : oauth2Logout.trim();
public String getOauthLogout() {
return oauthLogout == null ? "" : oauthLogout.trim();
}
public void setOauth2Logout(String oauth2Logout) {
this.oauth2Logout = oauth2Logout;
public void setOauthLogout(String oauthLogout) {
this.oauthLogout = oauthLogout;
}
public ClientProperties getClient() {

View File

@ -8,8 +8,8 @@ import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
@ -52,7 +52,7 @@ public class TransactionConfig {
@Autowired
private TransactionProperties transactionProperties;
@Autowired
private TransactionManager transactionManager;
private PlatformTransactionManager transactionManager;
/**
* 事务拦截器

View File

@ -47,7 +47,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
baseProperties.getLoginProcess(),
baseProperties.getLoginFailure(),
"/oauth/**",
"/oauth_client/**",
"/oauth2_client/**",
"/app/**",
"/approute/**",
"/wechat/**",

View File

@ -1,113 +0,0 @@
package ink.wgink.login.base.security;
import ink.wgink.common.handler.AccessDenyHandler;
import ink.wgink.login.base.handler.LoginFailureHandler;
import ink.wgink.login.base.handler.LogoutHandler;
import ink.wgink.login.base.security.user.UserSecurityConfig;
import ink.wgink.login.base.service.user.UserDetailServiceImpl;
import ink.wgink.login.base.service.user.UserLoginService;
import ink.wgink.properties.BaseProperties;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
/**
* @ClassName: WebSecurityConfig
* @Description: security配置
* @Author: WangGeng
* @Date: 2019/2/15 10:05 AM
* @Version: 1.0
**/
//@EnableWebSecurity
public class WebSecurityConfig1 {
@Autowired
private BaseProperties baseProperties;
@Autowired
private UserDetailServiceImpl userDetailService;
@Autowired
private UserLoginService userLoginService;
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
/**
* 默认放行配置
*/
String[] defaultAntMatchers = {
baseProperties.getLoginUrl(),
baseProperties.getLoginProcess(),
baseProperties.getLoginFailure(),
"/oauth/**",
"/oauth_client/**",
"/app/**",
"/approute/**",
"/wechat/**",
"/wechat-miniapp/**",
"/route/file/**",
"/api/sms/getverificationcode/*",
"/api/user/getsignintype/**"
};
String assetsMatchers = baseProperties.getAssetsMatchers();
String[] fullAntMatchers;
if (!StringUtils.isBlank(assetsMatchers)) {
String[] assetsMatchersArray = baseProperties.getAssetsMatchers().split(",");
fullAntMatchers = ArrayUtils.addAll(defaultAntMatchers, assetsMatchersArray);
} else {
fullAntMatchers = defaultAntMatchers;
}
LoginFailureHandler loginFailureHandler = new LoginFailureHandler(baseProperties.getLoginFailure());
http
.formLogin()
.loginPage(baseProperties.getLoginUrl())
.loginProcessingUrl(baseProperties.getLoginProcess())
.failureForwardUrl(baseProperties.getLoginUrl())
.failureHandler(loginFailureHandler)
.and()
.logout()
.addLogoutHandler(new LogoutHandler())
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers(fullAntMatchers)
.permitAll()
.and()
.authorizeRequests()
.anyRequest().access("@rbacService.hasPermission(request, authentication)")
.and()
.exceptionHandling().accessDeniedHandler(new AccessDenyHandler())
.and()
.cors()
.and()
.csrf()
.disable();
addUserAuthenticationFilter(http, loginFailureHandler);
return http.build();
}
/**
* 创建用户认证过滤器链替换原有UsernamePasswordAuthenticationFilter
*
* @param http
* @param loginFailureHandler
*/
private void addUserAuthenticationFilter(HttpSecurity http, LoginFailureHandler loginFailureHandler) throws Exception {
UserSecurityConfig userSecurityConfig = new UserSecurityConfig();
userSecurityConfig.setUserDetailService(userDetailService);
userSecurityConfig.setPasswordEncoder(passwordEncoder);
userSecurityConfig.setLoginProcessUrl(baseProperties.getLoginProcess());
userSecurityConfig.setLoginFailureHandler(loginFailureHandler);
userSecurityConfig.setUserLoginService(userLoginService);
http.apply(userSecurityConfig);
}
}

View File

@ -1,335 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<base th:href="${#request.getContextPath() + '/'}">
<title th:text="${systemTitle}"></title>
<meta charset="utf-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="icon" type="image/ico" href="assets/favicon.ico"/>
<link rel="stylesheet" href="assets/layuiadmin/layui/css/layui.css?v=3" media="all">
<link rel="stylesheet" href="assets/layuiadmin/style/admin.css?v=3" media="all">
<link rel="stylesheet" href="assets/css/supersized.css?v=3">
<link rel="stylesheet" href="assets/layuiadmin/style/login.css?v=3" media="all">
<style th:if="${loginBoxPosition eq 'center'}">
.layadmin-user-login-main {
left: 50%;
margin-left: -188px;
}
</style>
<style th:if="${loginBoxPosition eq 'left'}">
@media screen and (max-width: 1920px) {
.layadmin-user-login-main {
right: 68%;
}
}
@media screen and (max-width: 1366px) {
.layadmin-user-login-main {
right: 62%;
}
}
</style>
</head>
<body>
<div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login">
<div id="layadminUserLoginMain" class="layadmin-user-login-main" style="">
<div class="layadmin-user-login-box layadmin-user-login-header">
<img class="system-logo" th:src="'route/file/download/true/'+ ${systemLogo}" th:if="${systemLogo ne ''}">
<div th:class="${(systemLogo ne '') ? 'system-logo-title': ''}">
<h2 th:text="${systemTitle}" th:style="'font-size:'+ ${systemTitleSize} +'px'"></h2>
<p th:text="${systemSubTitle}" th:style="'font-size:'+ ${systemSubTitleSize} +'px'"></p>
</div>
</div>
<div id="userLoginBox" class="layadmin-user-login-box layadmin-user-login-body layui-form" lay-filter="LAY-form-signin">
<form id="LAY-form-signin" action="userlogin" method="post">
<input type="hidden" name="loginType" id="LAY-user-login-logintype" value="1">
<input type="hidden" name="uKey" id="LAY-user-login-ukey" value="">
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-username" for="LAY-user-login-username"></label>
<input type="text" name="username" id="LAY-user-login-username" lay-verify="required" placeholder="用户名" class="layui-input">
</div>
<div class="layui-form-item" id="passwordBox">
<label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label>
<input type="password" name="password" id="LAY-user-login-password" lay-verify="required" placeholder="密码" class="layui-input">
</div>
<div class="layui-form-item" id="verificationCodeBox" th:if="${verificationCode eq 'true'}">
<!-- 验证码 -->
<div class="layui-row">
<div class="layui-col-xs7">
<label class="layadmin-user-login-icon layui-icon layui-icon-vercode" for="LAY-user-login-vercode"></label>
<input type="text" name="verificationCode" id="LAY-user-login-vercode" lay-verify="required" placeholder="图形验证码" class="layui-input">
</div>
<div class="layui-col-xs5">
<div style="margin-left: 10px;">
<img src="oauth/verification-code/png" class="layadmin-user-login-codeimg" id="LAY-user-get-vercode">
</div>
</div>
</div>
</div>
<div class="layui-form-item u-key-box" id="uKeyBox" th:if="${uKeyLogin eq 'true'}">
<!-- ukey登录 -->
<span id="checkUKey">【检测UKey中...】</span>
<span class="get-u-key" id="getUKey">【UKey已插入】</span>
</div>
<div class="layui-form-item remember-password" id="rememberPassword">
<input type="checkbox" name="remember" lay-filter="LAY-user-login-remember" lay-skin="primary" title="记住密码">
<!--<a href="forget.html" class="layadmin-user-jump-change layadmin-link" style="margin-top: 7px;">忘记密码?</a>-->
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" id="LAY-user-login-submit" lay-submit lay-filter="LAY-user-login-submit">登 入</button>
</div>
<div id="otherLoginBox" class="other-login-box" th:if="${scanCodeLogin ne 'false'}">
<!-- 钉钉扫码 -->
<div th:if="${scanCodeLogin eq 'dingDingScanCode'}">
<img src="assets/images/login/dingding.png" style="width: 16px;">
<a href="javascript:void(0);" id="dingDingScanCodeBtn">钉钉扫码</a>
</div>
</div>
</form>
</div>
<div id="dingDingScanCodeBox" class="ding-ding-scan-code-box" th:if="${scanCodeLogin eq 'dingDingScanCode'}">
<!-- 钉钉扫码登录 -->
<div class="code-box">
<div id="dingDingScanCode"></div>
<a id="cancelDingDingScanCodeBtn" class="cancel-ding-ding-scan-code-btn" href="javascript:void(0);">取消扫码</a>
</div>
</div>
<input type="hidden" id="errorMessage" th:value="${errorMessage}"/>
</div>
<div class="layui-trans layadmin-user-login-footer footer-text">
<p th:if="${copyRightYear ne ''}"><span th:text="${'© '+ copyRightYear +' '}"></span><a href="javascript:void(0);" th:if="${copyleft ne ''}" th:text="${copyleft}"></a></p>
<p th:if="${officialUrl ne ''}">
<span><a th:href="@{${officialUrl}}" target="_blank">前往官网</a></span>
</p>
</div>
</div>
<div style="display: none">
<object type="application/x-dongle" id="uKey"></object>
</div>
<input type="hidden" id="serverUrl" th:value="${serverUrl +'/userlogin/dingding'}">
<input type="hidden" id="dingDingScanCodeAppId" th:value="${dingDingScanCodeAppId}" th:if="${scanCodeLogin eq 'dingDingScanCode'}">
<input type="hidden" id="loginBackgroundImages" th:value="${loginBackgroundImages}">
<script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js" th:if="${scanCodeLogin eq 'dingDingScanCode'}"></script>
<script src="assets/layuiadmin/layui/layui.js"></script>
<script>
layui.config({
base: 'assets/layuiadmin/'
}).extend({
index: 'lib/index'
}).use(['index', 'user', 'cookie', 'md5', 'base64', 'supersized', 'common', 'ftukey', 'restajax'], function () {
var $ = layui.$;
var form = layui.form;
var layer = layui.layer;
var cookie = layui.cookie;
var md5 = layui.md5;
var base64 = layui.base64;
var common = layui.common;
var ftukey = layui.ftukey;
var restAjax = layui.restajax;
var isCheckLoginType = false;
var pageData = {
remember: false
};
// 显示错误信息
var errorMessage = $('#errorMessage').val();
if (errorMessage != '') {
layer.msg(errorMessage);
}
// 初始化背景
function initBackground() {
var loginBackgroundImages = $('#loginBackgroundImages').val();
var photos = [];
if(loginBackgroundImages != '') {
var loginBackgroundImageArray = loginBackgroundImages.split(',');
for(var i = 0, item = loginBackgroundImageArray[i]; item = loginBackgroundImageArray[i++];) {
photos.push({
image: 'route/file/download/true/'+ item
})
}
} else {
for(var i = 1; i <= 9; i++) {
photos.push({
image: 'assets/images/backgrounds/'+ i +'.jpg'
})
}
}
$.supersized({
slide_interval : 4000, // Length between transitions
transition : 1, // 0-None, 1-Fade, 2-Slide Top, 3-Slide Right, 4-Slide Bottom, 5-Slide Left, 6-Carousel Right, 7-Carousel Left
transition_speed : 1000, // Speed of transition
performance : 1, // 0-Normal, 1-Hybrid speed/quality, 2-Optimizes image quality, 3-Optimizes transition speed // (Only works for Firefox/IE, not Webkit)
// Size & Position
min_width : 0, // Min width allowed (in pixels)
min_height : 0, // Min height allowed (in pixels)
vertical_center : 1, // Vertically center background
horizontal_center : 1, // Horizontally center background
fit_always : 0, // Image will never exceed browser width or height (Ignores min. dimensions)
fit_portrait : 1, // Portrait images will not exceed browser height
fit_landscape : 0, // Landscape images will not exceed browser width
// Components
slide_links : 'blank', // Individual links for each slide (Options: false, 'num', 'name', 'blank')
slides : photos
});
}
initBackground();
// 记住密码
form.on('checkbox(LAY-user-login-remember)', function(data){
pageData.remember = data.elem.checked;
});
//提交
form.on('submit(LAY-user-login-submit)', function (obj) {
if(pageData.remember) {
cookie.setCookie('rememberMe', base64.encode(obj.field.username +'_$cm$_'+ obj.field.password), 30);
} else {
cookie.setCookie('rememberMe', '', 0);
}
$('#LAY-user-login-password').val(md5(md5(md5(obj.field.password))));
layer.msg('正在登录,请稍后...', {icon: 16, shade: 0.1, time: 0});
});
$('#dingDingScanCodeBtn').on('click', function() {
$('#userLoginBox').hide();
var url = encodeURIComponent($('#serverUrl').val());
var appId = $('#dingDingScanCodeAppId').val();
var obj = DDLogin({
id: 'dingDingScanCode',
goto: encodeURIComponent('https://oapi.dingtalk.com/connect/qrconnect?appid='+ appId +'&response_type=code&scope=snsapi_login&state=STATE&redirect_uri='+ url),
style: 'border:none; background-color:#FFFFFF;',
width : '100%',
height: '320'
});
$('#dingDingScanCodeBox').show();
var handleMessage = function (event) {
var origin = event.origin;
if( origin == "https://login.dingtalk.com" ) {
var loginTmpCode = event.data;
var snsAuthorizeHref = 'https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid='+ appId +'&response_type=code&scope=snsapi_login&state=STATE&redirect_uri='+ url +'&loginTmpCode='+ loginTmpCode;
window.location.href = snsAuthorizeHref;
}
};
// 添加监听事件
if (typeof window.addEventListener != 'undefined') {
window.addEventListener('message', handleMessage, false);
} else if (typeof window.attachEvent != 'undefined') {
window.attachEvent('onmessage', handleMessage);
}
});
$('#cancelDingDingScanCodeBtn').on('click', function() {
$('#dingDingScanCodeBox').hide();
$('#userLoginBox').show();
});
// 记住我
var rememberMe = cookie.getCookie('rememberMe');
if(null != rememberMe && '' != rememberMe) {
var rememberMes = base64.decode(rememberMe).split('_$cm$_');
$('#LAY-user-login-username').val(rememberMes[0]);
$('#LAY-user-login-password').val(rememberMes[1])
pageData.remember = true;
form.val('LAY-form-signin', {
remember: true
});
}
// 刷新表单
form.render();
// 初始化表单
function initLoginForm(uKeyData) {
if(uKeyData.UserLoginName.length == 0) {
return;
}
$('#LAY-user-login-username').val(uKeyData.UserLoginName);
var loadLayerIndex;
restAjax.get(restAjax.path('api/user/getsignintype/{userUsername}', [uKeyData.UserLoginName]), {}, null, function(code, data) {
if(data.data != 1 && data.data != 2 && data.data != 3) {
layer.msg('用户登录类型错误,请使用用户名和密码登录');
return;
}
$('#LAY-user-login-logintype').val(data.data);
if(data.data == 1) {
return;
}
// 隐藏验证码
if($('#verificationCodeBox')) {
$('#verificationCodeBox').hide();
}
$('#LAY-user-login-vercode').removeAttr('lay-verify');
if(data.data === 2) {
return;
}
$('#LAY-user-login-password').removeAttr('lay-verify');
$('#passwordBox').hide();
$('#rememberPassword').hide();
$('#uKeyBox').css('marginBottom', '20px');
if(data.data === 3) {
return;
}
}, function(code, data) {
layer.msg(data.msg);
}, function() {
loadLayerIndex = layer.msg('查询登录类型,请稍后...', {icon: 16, time: 0, shade: 0.3});
}, function() {
layer.close(loadLayerIndex);
});
}
// 检查UKey
function checkUKey() {
var ukeyData = ftukey.ReadUKeyData();
if(ukeyData.success && ukeyData.data.length != 0) {
if(!isCheckLoginType) {
isCheckLoginType = true;
$('#LAY-form-signin').attr('action', 'userlogin/ukey');
$('#checkUKey').hide();
$('#getUKey').show();
setTimeout(function() {
var data = ftukey.ParseJson(ukeyData.data);
$('#LAY-user-login-ukey').val(ukeyData.hid);
initLoginForm(data);
}, 500);
}
} else {
if(isCheckLoginType) {
isCheckLoginType = false;
$('#LAY-form-signin').attr('action', 'userlogin');
$('#LAY-user-login-vercode').attr('lay-verify', 'required');
$('#LAY-user-login-password').attr('lay-verify', 'required');
if($('#verificationCodeBox')) {
$('#verificationCodeBox').show();
}
$('#passwordBox').show();
$('#rememberPassword').show();
$('#uKeyBox').css('marginBottom', '0px');
$('#getUKey').hide();
$('#checkUKey').show();
}
}
setTimeout(function() {
checkUKey();
}, 1000);
}
// 初始化UKey监听器
function initUKeyListener() {
if (common.getBrowserType() != 'IE') {
return;
}
if (common.getIEBrowserVersion() != '11') {
return;
}
$('#uKeyBox').show();
setTimeout(function() {
checkUKey();
}, 1000);
}
if($('#uKeyBox')) {
initUKeyListener();
}
});
</script>
</body>
</html>

View File

@ -21,12 +21,27 @@
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>

View File

@ -35,13 +35,13 @@ public class OAuth2ClientConfig extends WebSecurityConfigurerAdapter {
.formLogin()
.defaultSuccessUrl("/authorize", true)
.and()
.logout().logoutSuccessUrl(oAuth2ClientProperties.getOauth2Logout())
.logout().logoutSuccessUrl(oAuth2ClientProperties.getOauthLogout())
.and()
.authorizeRequests().antMatchers("/app/**","/resource/**", "/route/file/**").permitAll()
.and()
.authorizeRequests()
.anyRequest()
.access("@rbacService.hasPermission(request, authentication)")
.access("@clientRbacService.hasPermission(request, authentication)")
.and()
.headers().frameOptions().sameOrigin()
.and()

View File

@ -0,0 +1,120 @@
package ink.wgink.login.oauth2.client.controller.route;
import ink.wgink.common.component.SecurityComponent;
import ink.wgink.interfaces.menu.IMenuBaseService;
import ink.wgink.interfaces.role.IRoleMenuBaseService;
import ink.wgink.pojo.bos.UserInfoBO;
import ink.wgink.properties.ServerProperties;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
/**
* When you feel like quitting. Think about why you started
* 当你想要放弃的时候想想当初你为何开始
*
* @ClassName: IndexRouteController
* @Description: index
* @Author: wanggeng
* @Date: 2021/2/27 3:38 下午
* @Version: 1.0
*/
@Controller
public class IndexRouteController {
@Autowired
private SecurityComponent securityComponent;
@Autowired
private ServerProperties serverProperties;
@Autowired(required = false)
private IMenuBaseService menuBaseService;
@Autowired(required = false)
private IRoleMenuBaseService roleMenuBaseService;
@GetMapping("index")
public ModelAndView goIndex() {
if (!StringUtils.isBlank(serverProperties.getDefaultIndexPage())) {
return new ModelAndView(new RedirectView(serverProperties.getDefaultIndexPage()));
}
return new ModelAndView("forward:/default-main");
}
/**
* 默认主页
*
* @return
*/
@GetMapping("default-main")
public ModelAndView defaultMain() {
ModelAndView mv = new ModelAndView("default-main");
UserInfoBO userInfoBO = securityComponent.getCurrentUser();
mv.addObject("userUsername", userInfoBO.getUserUsername());
// Map<String, Object> config = ConfigManager.getInstance().getConfig();
// // 先加载系统短标题没有加载主标题没有加载配置文件系统标题
// if (!Objects.isNull(config.get(IUserCenterConst.SYSTEM_SHORT_TITLE)) && !StringUtils.isBlank(config.get(IUserCenterConst.SYSTEM_SHORT_TITLE).toString())) {
// mv.addObject(IUserCenterConst.SYSTEM_SHORT_TITLE, config.get(IUserCenterConst.SYSTEM_SHORT_TITLE).toString());
// } else if(!Objects.isNull(config.get(IUserCenterConst.SYSTEM_TITLE)) && !StringUtils.isBlank(config.get(IUserCenterConst.SYSTEM_TITLE).toString())) {
// mv.addObject(IUserCenterConst.SYSTEM_SHORT_TITLE, config.get(IUserCenterConst.SYSTEM_TITLE).toString());
// } else {
// mv.addObject(IUserCenterConst.SYSTEM_SHORT_TITLE, serverProperties.getSystemTitle());
// }
// // 系统短LOGO
// if (!Objects.isNull(config.get(IUserCenterConst.SYSTEM_SHORT_LOGO)) && !StringUtils.isBlank(config.get(IUserCenterConst.SYSTEM_SHORT_LOGO).toString())) {
// mv.addObject(IUserCenterConst.SYSTEM_SHORT_LOGO, config.get(IUserCenterConst.SYSTEM_SHORT_LOGO).toString());
// } else {
// mv.addObject(IUserCenterConst.SYSTEM_SHORT_LOGO, "");
// }
// mv.addObject("ws", serverProperties.getWs());
// // 菜单模式
// if (!Objects.isNull(config.get(IUserCenterConst.MENU_MODE)) && !StringUtils.isBlank(config.get(IUserCenterConst.MENU_MODE).toString())) {
// mv.addObject(IUserCenterConst.MENU_MODE, config.get(IUserCenterConst.MENU_MODE).toString());
// }
// if (menuBaseService != null) {
// List<MenuDTO> menus;
// if (StringUtils.equalsIgnoreCase(ISystemConstant.ADMIN, userInfoBO.getUserUsername())) {
// // 管理员
// List<String> menuIds = roleMenuBaseService.listMenuId(ISystemConstant.ADMIN);
// if (menuIds.isEmpty()) {
// menus = menuBaseService.listAllByParentId(IMenuBaseService.MENU_UNIFIED_USER);
// } else {
// menus = menuBaseService.listAllByParentIdAndIds(IMenuBaseService.MENU_UNIFIED_USER, menuIds);
// }
// } else {
// // 普通用户
// List<RoleSimpleDTO> roleSimpleDTOs = securityComponent.getCurrentUser().getRoles();
// if (roleSimpleDTOs.isEmpty()) {
// menus = new ArrayList<>();
// } else {
// List<String> roleIds = new ArrayList<>();
// for (RoleSimpleDTO roleSimpleDTO : roleSimpleDTOs) {
// roleIds.add(roleSimpleDTO.getRoleId());
// }
// List<String> menuIds = roleMenuBaseService.listMenuId(roleIds);
// menus = menuBaseService.listAllByParentIdAndIds(IMenuBaseService.MENU_UNIFIED_USER, menuIds);
// }
// }
// mv.addObject("menus", menus);
// }
return mv;
}
/**
* 默认导航首页
*
* @return
*/
@GetMapping("default-home")
public ModelAndView defaultHome() {
ModelAndView mv;
if (!StringUtils.isBlank(serverProperties.getDefaultHomePage())) {
mv = new ModelAndView(new RedirectView(serverProperties.getDefaultHomePage()));
} else {
mv = new ModelAndView("default-home");
}
return mv;
}
}

View File

@ -1,5 +1,6 @@
package ink.wgink.login.oauth2.client.converter;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.pojo.bos.UserInfoBO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -36,7 +37,7 @@ public class OAuth2ClientUserAuthConverter implements UserAuthenticationConverte
if (!Objects.isNull(principal)) {
Collection<GrantedAuthority> authorities = new ArrayList<>();
// 包含用户信息则直接抽取其中的用户信息
UserInfoBO userInfoBO = (UserInfoBO) map.get("user_info");
UserInfoBO userInfoBO = JSONObject.parseObject(map.get("user_info").toString(), UserInfoBO.class);
principal = userInfoBO;
LOG.debug("获取用户权限");
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<base th:href="${#request.getContextPath() + '/'}">
<meta charset="utf-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="stylesheet" href="assets/fonts/font-awesome/css/font-awesome.css"/>
<link rel="stylesheet" href="assets/layuiadmin/layui/css/layui.css" media="all">
<link rel="stylesheet" href="assets/layuiadmin/style/admin.css" media="all">
</head>
<body>
<div id="LAY-app" class="layui-fluid">
<div class="layui-row layui-col-space15">
<h1>欢迎使用</h1>
</div>
</div>
</div>
<script type="text/javascript" src="assets/js/vue.min.js"></script>
<script type="text/javascript" src="assets/js/vendor/echarts/echarts.min.js"></script>
<script src="assets/layuiadmin/layui/layui.js"></script>
<script>
layui.config({
base: 'assets/layuiadmin/' //静态资源所在路径
}).extend({
index: 'lib/index' //主入口模块
}).use(['index', 'animate-numbers'], function() {
var $ = layui.$;
var $win = $(window);
});
</script>
</body>
</html>

View File

@ -0,0 +1,248 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<base th:href="${#request.getContextPath() + '/'}">
<title th:text="${systemTitle}"></title>
<meta charset="utf-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="icon" type="image/ico" href="assets/favicon.ico"/>
<link rel="stylesheet" href="assets/fonts/font-awesome/css/font-awesome.css">
<link rel="stylesheet" href="assets/layuiadmin/layui/css/layui.css" media="all">
<link rel="stylesheet" href="assets/layuiadmin/style/admin.css" media="all">
<link rel="stylesheet" href="assets/layuiadmin/style/default-main.css" media="all">
<style th:if="${menuMode eq 'floatLeft'}">
.layui-side-menu-hidden {display: none;}
.layui-layout-admin .layui-layout-left, .layadmin-pagetabs, .layui-layout-admin .layui-body, .layui-layout-admin .layui-footer {left: 0px}
@media screen and (max-width: 992px) {
.layui-layout-admin .layui-side {
transform: none;
-webkit-transform: none;
width: 220px;
}
.layui-layout-admin .layui-layout-left,
.layadmin-pagetabs,
.layui-layout-admin .layui-body,
.layui-layout-admin .layui-footer {
left: 0;
}
}
</style>
</head>
<body class="layui-layout-body">
<div id="LAY_app">
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<!-- 头部区域 -->
<ul class="layui-nav layui-layout-left">
<li class="layui-nav-item layadmin-flexible layui-side-menu-show" lay-unselect>
<a href="javascript:void(0);" layadmin-event="flexible" title="侧边伸缩">
<i class="layui-icon layui-icon-shrink-right" id="LAY_app_flexible"></i>
</a>
</li>
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="index" title="首页">
<i class="layui-icon layui-icon-website"></i>
</a>
</li>
<li class="layui-nav-item" lay-unselect>
<a href="javascript:void(0);" layadmin-event="refresh" title="刷新">
<i class="layui-icon layui-icon-refresh-3"></i>
</a>
</li>
</ul>
<ul class="layui-nav layui-layout-right" lay-filter="layadmin-layout-right">
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="javascript:void(0);" layadmin-event="theme">
<i class="layui-icon layui-icon-theme"></i>
</a>
</li>
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="javascript:void(0);" layadmin-event="note">
<i class="layui-icon layui-icon-note"></i>
</a>
</li>
<li class="layui-nav-item" lay-unselect>
<a href="javascript:void(0);">
<cite th:text="${userUsername}"></cite>
</a>
<dl class="layui-nav-child">
<dd><a id="LAY-changePassword" lay-href="javascript:void(0);">修改密码</a></dd>
<hr>
<dd id="LAY-logout" style="text-align: center;"><a href="javascript:void(0);">退出</a></dd>
</dl>
</li>
<li class="layui-nav-item layui-hide-xs" lay-unselect>
<a href="javascript:void(0);" layadmin-event="fullscreen">
<i class="layui-icon layui-icon-screen-full"></i>
</a>
</li>
<li class="layui-nav-item layui-show-xs-inline-block layui-hide-sm" lay-unselect>
<a href="javascript:void(0);" layadmin-event="more"><i class="layui-icon layui-icon-more-vertical"></i></a>
</li>
</ul>
</div>
<!-- 侧边菜单 -->
<div id="sideMenu" class="layui-side layui-side-menu layui-side-menu-hidden">
<div class="layui-side-scroll">
<div class="layui-logo">
<img th:src="'route/file/download/true/'+ ${systemShortLogo}" style="width: 32px; height: 32px;" th:if="${systemShortLogo ne ''}">
<span th:text="${systemShortTitle}" style="font-weight: bold;"></span>
</div>
<ul class="layui-nav layui-nav-tree" lay-shrink="all" id="LAY-system-side-menu" lay-filter="layadmin-system-side-menu">
<li data-name="component" class="layui-nav-item" th:each="menu1: ${menus}" th:title="${menu1.menuSummary}">
<a href="javascript:void(0);" th:if="${!#lists.isEmpty(menu1.subMenus)}" th:attr="lay-tips=${menu1.menuName}" lay-direction="2">
<span class="layui-icon">
<i th:class="${menu1.menuIcon}"></i>
</span>
<cite th:text="${menu1.menuName}"></cite>
</a>
<a href="javascript:void(0);" th:if="${#lists.isEmpty(menu1.subMenus)}" th:attr="lay-tips=${menu1.menuName},lay-href=${menu1.menuUrl} ,lay-open-type=${menu1.openType}" lay-direction="2">
<span class="layui-icon">
<i th:class="${menu1.menuIcon}"></i>
</span>
<cite th:text="${menu1.menuName}"></cite>
</a>
<dl class="layui-nav-child" th:if="${!#lists.isEmpty(menu1.subMenus)}">
<dd data-name="grid" th:each="menu2: ${menu1.subMenus}" th:title="${menu2.menuSummary}">
<a href="javascript:void(0);" th:if="${!#lists.isEmpty(menu2.subMenus)}">
<span class="layui-icon">
<i th:class="${menu2.menuIcon}"></i>
</span>
<cite th:text="${menu2.menuName}"></cite>
</a>
<a href="javascript:void(0);" th:if="${#lists.isEmpty(menu2.subMenus)}" th:attr="lay-href=${menu2.menuUrl}, lay-open-type=${menu2.openType}">
<span class="layui-icon">
<i th:class="${menu2.menuIcon}"></i>
</span>
<cite th:text="${menu2.menuName}"></cite>
</a>
<dl class="layui-nav-child" th:if="${!#lists.isEmpty(menu2.subMenus)}">
<dd data-name="list" th:each="menu3: ${menu2.subMenus}" th:title="${menu3.menuSummary}">
<a href="javascript:void(0)" th:text="${menu3.menuName}" th:attr="lay-href=${menu3.menuUrl}, lay-open-type=${menu3.openType}"></a>
</dd>
</dl>
</dd>
</dl>
</li>
</ul>
</div>
</div>
<!-- 页面标签 -->
<div class="layadmin-pagetabs auto-hide-menu" id="LAY_app_tabs">
<div class="layui-icon layadmin-tabs-control layui-icon-prev" layadmin-event="leftPage"></div>
<div class="layui-icon layadmin-tabs-control layui-icon-next" layadmin-event="rightPage"></div>
<div class="layui-icon layadmin-tabs-control layui-icon-down">
<ul class="layui-nav layadmin-tabs-select" lay-filter="layadmin-pagetabs-nav">
<li class="layui-nav-item" lay-unselect>
<a href="javascript:void(0);"></a>
<dl class="layui-nav-child layui-anim-fadein">
<dd layadmin-event="closeThisTabs"><a href="javascript:void(0);">关闭当前标签页</a></dd>
<dd layadmin-event="closeOtherTabs"><a href="javascript:void(0);">关闭其它标签页</a></dd>
<dd layadmin-event="closeAllTabs"><a href="javascript:void(0);">关闭全部标签页</a></dd>
</dl>
</li>
</ul>
</div>
<div class="layui-tab" lay-unauto lay-allowClose="true" lay-filter="layadmin-layout-tabs">
<ul class="layui-tab-title" id="LAY_app_tabsheader">
<li lay-id="default.html" lay-attr="default.html" class="layui-this"><i class="layui-icon layui-icon-home"></i></li>
</ul>
</div>
</div>
<!-- 主体内容 -->
<div class="layui-body auto-hide-menu" id="LAY_app_body">
<div class="layadmin-tabsbody-item layui-show">
<iframe id="defaultIFrame" frameborder="0" class="layadmin-iframe"></iframe>
</div>
</div>
<!-- 辅助元素,一般用于移动设备下遮罩 -->
<div class="layadmin-body-shade" layadmin-event="shade"></div>
</div>
</div>
<script src="assets/layuiadmin/layui/layui.js"></script>
<script>
layui.config({
base: 'assets/layuiadmin/' //静态资源所在路径
}).extend({
index: 'lib/index' //主入口模块
}).use(['index', 'element', 'restajax', 'datamessage', 'dialog'], function() {
var element = layui.element;
var $ = layui.$;
var layer = layui.layer;
window.dialog = layui.dialog;
window.restAjax = layui.restajax;
window.dataMessage = layui.datamessage;
function changePassword() {
top.dialog.open({
url: top.restAjax.path('route/user/update-password', []),
title: '修改密码',
width: '400px',
height: '290px',
onClose: function() {}
});
}
function checkPasswordStatus() {
top.restAjax.get(top.restAjax.path('api/user/get-password-status', []), {}, null, function(code, data) {
if(data.data == 'change') {
changePassword();
} else if(data.data == 'remind') {
layer.open({
title: '警告!',
content: '密码已过期,为确保账号安全,请尽快修改密码!',
auto: ['100px', '80px'],
offset: 'rb'
});
}
}, function(code, data) {
top.dialog.message(data.msg);
});
}
checkPasswordStatus();
$('#LAY-changePassword').on('click', function() {
changePassword()
});
$('#defaultIFrame').attr('src', 'default-home');
$('#LAY-logout').on('click', function() {
top.dialog.confirm('确认退出?', function() {
window.location.href = 'oauth/logout';
});
});
/** 左浮动菜单 start **/
var hideSideMenuTimeout = null;
$('.layui-side-menu-show').on('mouseover', function(event) {
$('#sideMenu').removeClass('layui-side-menu-hidden');
});
$('#sideMenu').on('mouseout', function(event) {
if(hideSideMenuTimeout) {
return;
}
hideSideMenuTimeout = setTimeout(function() {
$('#sideMenu').addClass('layui-side-menu-hidden')
}, 100);
});
$('#sideMenu').on('mouseover', function(event) {
if(hideSideMenuTimeout) {
clearTimeout(hideSideMenuTimeout);
hideSideMenuTimeout = null;
}
});
/** 左浮动菜单 end **/
});
</script>
</body>
</html>

View File

@ -26,6 +26,12 @@
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
@ -37,6 +43,10 @@
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>

View File

@ -2,13 +2,15 @@ package ink.wgink.login.oauth2.server.config;
import ink.wgink.login.base.service.user.UserDetailServiceImpl;
import ink.wgink.login.oauth2.server.converter.UserAccessTokenConverter;
import ink.wgink.login.oauth2.server.service.impl.Oauth2ClientDetailsServiceImpl;
import ink.wgink.login.oauth2.server.service.impl.Oauth2ClientTokenServiceImpl;
import ink.wgink.login.oauth2.server.service.impl.OAuth2ClientDetailsServiceImpl;
import ink.wgink.login.oauth2.server.service.impl.OAuth2ClientTokenServiceImpl;
import ink.wgink.service.user.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
@ -52,14 +54,18 @@ public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigur
@Autowired
private IUserService userService;
@Autowired
private Oauth2ClientDetailsServiceImpl oAuth2ClientDetailsService;
private OAuth2ClientDetailsServiceImpl oAuth2ClientDetailsService;
@Autowired
private OAuth2ClientTokenServiceImpl oAuth2ClientTokenService;
@Autowired
private Oauth2ClientTokenServiceImpl oAuth2ClientTokenService;
private AuthenticationManager authenticationManager;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 通过内存的方式来完成认证服务
// 通过业务代码完成认证
clients.withClientDetails(oAuth2ClientDetailsService);
}
@ -76,12 +82,12 @@ public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigur
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
// 添加JWT授权机制
endpoints
.pathMapping("/oauth/authorize", "/oauth_client/authorize")
.pathMapping("/oauth/token", "/oauth_client/token")
.pathMapping("/oauth/token_key", "/oauth_client/token_key")
.pathMapping("/oauth/check_token", "/oauth_client/check_token")
.pathMapping("/oauth/confirm_access", "/oauth_client/confirm_access")
.pathMapping("/oauth/error", "/oauth_client/error")
.pathMapping("/oauth/authorize", "/oauth2_client/authorize")
.pathMapping("/oauth/token", "/oauth2_client/token")
.pathMapping("/oauth/token_key", "/oauth2_client/token_key")
.pathMapping("/oauth/check_token", "/oauth2_client/check_token")
.pathMapping("/oauth/confirm_access", "/oauth2_client/confirm_access")
.pathMapping("/oauth/error", "/oauth2_client/error")
.authenticationManager(authenticationManager)
.tokenStore(jwtTokenStore())
.accessTokenConverter(jwtAccessTokenConverter())
@ -152,5 +158,13 @@ public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigur
return tokenStoreUserApprovalHandler;
}
public static void main(String[] args) {
PasswordEncoder pe = new BCryptPasswordEncoder();
System.out.println(pe.encode("dGdIYVJvaEwvdFovRG01REtyYmVuMi9xMXVzR0lIYmxoNmdSZ3R6MWE4WGxIdG9KZmEyTjJIRnI0dG1McEdEVA=="));
// boolean matches = pe.matches("dGdIYVJvaEwvdFovRG01REtyYmVuMi9xMXVzR0lIYmxoNmdSZ3R6MWE4WGxIdG9KZmEyTjJIRnI0dG1McEdEVA==", "$2a$10$.IjNieJzGGpC0Vv3gjIWB.Zjj1pi9VZS8M96zbbv3qj8gDsYX./h6");
boolean matches = pe.matches("dGdIYVJvaEwvdFovRG01REtyYmVuMi9xMXVzR0lIYmxoNmdSZ3R6MWE4WGxIdG9KZmEyTjJIRnI0dG1McEdEVA==", "$2a$10$nopzmRLR8HneFyw2uccG9u3XNnK4Cupzb4BR0m/WzSGucB2Rsxwxq");
System.out.println(matches);
}
}

View File

@ -0,0 +1,27 @@
package ink.wgink.login.oauth2.server.config;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import ink.wgink.login.oauth2.server.converter.OAuth2AccessTokenMessageConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* @ClassName: OAuth2WebMvcConfigurer
* @Description: OAuth2配置该类是为了处理由于使用了Fastjson而不是原本的Jackson所导致的token格式变化
* @Author: wanggeng
* @Date: 2021/9/18 1:10 下午
* @Version: 1.0
*/
@Configuration
public class OAuth2WebMvcConfigurer implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new FastJsonHttpMessageConverter());
converters.add(0, new OAuth2AccessTokenMessageConverter());
}
}

View File

@ -0,0 +1,60 @@
package ink.wgink.login.oauth2.server.converter;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @ClassName: OAuth2AccessTokenMessageConverter
* @Description: OAuth2的Token解析类
* @Author: wanggeng
* @Date: 2021/9/18 1:11 下午
* @Version: 1.0
*/
public class OAuth2AccessTokenMessageConverter extends AbstractHttpMessageConverter<OAuth2AccessToken> {
private final FastJsonHttpMessageConverter delegateMessageConverter;
public OAuth2AccessTokenMessageConverter() {
super(MediaType.APPLICATION_JSON);
this.delegateMessageConverter = new FastJsonHttpMessageConverter();
}
@Override
protected boolean supports(Class<?> clazz) {
return OAuth2AccessToken.class.isAssignableFrom(clazz);
}
@Override
protected OAuth2AccessToken readInternal(Class<? extends OAuth2AccessToken> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
throw new UnsupportedOperationException("This converter is only used for converting from externally aqcuired form data");
}
@Override
protected void writeInternal(OAuth2AccessToken accessToken, HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
Map<String, Object> data = new HashMap<>(8);
data.put(OAuth2AccessToken.ACCESS_TOKEN, accessToken.getValue());
data.put(OAuth2AccessToken.TOKEN_TYPE, accessToken.getTokenType());
data.put(OAuth2AccessToken.EXPIRES_IN, accessToken.getExpiresIn());
data.put(OAuth2AccessToken.SCOPE, String.join(" ", accessToken.getScope()));
OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
if (Objects.nonNull(refreshToken)) {
data.put(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue());
}
delegateMessageConverter.write(data, MediaType.APPLICATION_JSON, outputMessage);
}
}

View File

@ -1,5 +1,6 @@
package ink.wgink.login.oauth2.server.converter;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.pojo.bos.LoginUser;
import ink.wgink.pojo.bos.UserInfoBO;
import ink.wgink.service.user.service.IUserService;
@ -46,7 +47,7 @@ public class UserAuthConverter implements UserAuthenticationConverter {
userInfoBO.setGroups(loginUser.getGroups());
userInfoBO.setPositions(loginUser.getPositions());
userInfoBO.setExpandData(loginUser.getExpandData());
response.put("user_info", userInfoBO);
response.put("user_info", JSONObject.toJSONString(userInfoBO));
return response;
}

View File

@ -0,0 +1,427 @@
package ink.wgink.login.oauth2.server.endpoint;
import ink.wgink.login.oauth2.server.exceptions.OAuth2ClientBadClientCredentialsException;
import ink.wgink.login.oauth2.server.service.impl.OAuth2ClientDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.*;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.endpoint.AbstractEndpoint;
import org.springframework.security.oauth2.provider.endpoint.DefaultRedirectResolver;
import org.springframework.security.oauth2.provider.endpoint.RedirectResolver;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenRequest;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpSessionRequiredException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.security.Principal;
import java.util.*;
/**
* When you feel like quitting. Think about why you started
* 当你想要放弃的时候想想当初你为何开始
*
* @ClassName: OauthClientAuthorizationEndpoint
* @Description: Oauth客户端认证端口
* @Author: WangGeng
* @Date: 2020/7/23 8:25 下午
* @Version: 1.0
**/
@Controller
@SessionAttributes("authorizationRequest")
public class OAuth2ClientAuthorizationEndpoint extends AbstractEndpoint {
private RedirectResolver redirectResolver = new DefaultRedirectResolver();
private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
private OAuth2RequestValidator oauth2RequestValidator = new DefaultOAuth2RequestValidator();
private String userApprovalPage = "error/oauth_confirm_access";
private String errorPage = "error/oauth_error";
private Object implicitLock = new Object();
@Autowired
private UserApprovalHandler userApprovalHandler;
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
private OAuth2ClientDetailsServiceImpl oauthClientDetailsService;
@Autowired
private TokenGranter tokenGranter;
@Override
public void afterPropertiesSet() throws Exception {
setTokenGranter(tokenGranter);
setClientDetailsService(oauthClientDetailsService);
super.afterPropertiesSet();
}
@RequestMapping(value = "/oauth2_client/authorize")
public ModelAndView authorize(Map<String, Object> model,
@RequestParam Map<String, String> parameters,
SessionStatus sessionStatus,
Principal principal) {
AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);
Set<String> responseTypes = authorizationRequest.getResponseTypes();
if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
throw new UnsupportedResponseTypeException("响应类型不支持: " + responseTypes);
}
if (authorizationRequest.getClientId() == null) {
throw new InvalidClientException("客户端不存在。");
}
try {
if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
throw new InsufficientAuthenticationException("系统认证完成后才能进行用户认证。");
}
ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
if (!StringUtils.hasText(resolvedRedirect)) {
throw new RedirectMismatchException("客户端必须提供重定向URI。");
}
authorizationRequest.setRedirectUri(resolvedRedirect);
oauth2RequestValidator.validateScope(authorizationRequest, client);
authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest, (Authentication) principal);
boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
authorizationRequest.setApproved(approved);
if (authorizationRequest.isApproved()) {
if (responseTypes.contains("token")) {
return getImplicitGrantResponse(authorizationRequest);
}
if (responseTypes.contains("code")) {
return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
(Authentication) principal));
}
}
model.put("authorizationRequest", authorizationRequest);
return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);
} catch (RuntimeException e) {
sessionStatus.setComplete();
throw e;
}
}
@RequestMapping(value = "/oauth2_client/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)
public View approveOrDeny(@RequestParam Map<String, String> approvalParameters,
Map<String, ?> model,
SessionStatus sessionStatus,
Principal principal) {
if (!(principal instanceof Authentication)) {
sessionStatus.setComplete();
throw new InsufficientAuthenticationException(
"在授权访问令牌之前,必须使系统对用户进行身份验证。");
}
AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");
if (authorizationRequest == null) {
sessionStatus.setComplete();
throw new InvalidRequestException("无法批准未初始化的授权请求。");
}
try {
Set<String> responseTypes = authorizationRequest.getResponseTypes();
authorizationRequest.setApprovalParameters(approvalParameters);
authorizationRequest = userApprovalHandler.updateAfterApproval(authorizationRequest,
(Authentication) principal);
boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
authorizationRequest.setApproved(approved);
if (authorizationRequest.getRedirectUri() == null) {
sessionStatus.setComplete();
throw new InvalidRequestException("未提供用于重定向的URI。");
}
if (!authorizationRequest.isApproved()) {
return new RedirectView(getUnsuccessfulRedirect(authorizationRequest,
new UserDeniedAuthorizationException("用户拒绝访问。"), responseTypes.contains("token")),
false, true, false);
}
if (responseTypes.contains("token")) {
return getImplicitGrantResponse(authorizationRequest).getView();
}
return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);
} finally {
sessionStatus.setComplete();
}
}
private ModelAndView getUserApprovalPageResponse(Map<String, Object> model,
AuthorizationRequest authorizationRequest, Authentication principal) {
logger.debug("Loading user approval page: " + userApprovalPage);
model.putAll(userApprovalHandler.getUserApprovalRequest(authorizationRequest, principal));
return new ModelAndView(userApprovalPage, model);
}
private ModelAndView getImplicitGrantResponse(AuthorizationRequest authorizationRequest) {
try {
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(authorizationRequest, "implicit");
OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest);
OAuth2AccessToken accessToken = getAccessTokenForImplicitGrant(tokenRequest, storedOAuth2Request);
if (accessToken == null) {
throw new UnsupportedResponseTypeException("不支持的响应类型: token");
}
return new ModelAndView(new RedirectView(appendAccessToken(authorizationRequest, accessToken), false, true,
false));
} catch (OAuth2Exception e) {
return new ModelAndView(new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, true), false,
true, false));
}
}
private OAuth2AccessToken getAccessTokenForImplicitGrant(TokenRequest tokenRequest,
OAuth2Request storedOAuth2Request) {
OAuth2AccessToken accessToken = null;
synchronized (this.implicitLock) {
accessToken = getTokenGranter().grant("implicit",
new ImplicitTokenRequest(tokenRequest, storedOAuth2Request));
}
return accessToken;
}
private View getAuthorizationCodeResponse(AuthorizationRequest authorizationRequest, Authentication authUser) {
try {
return new RedirectView(getSuccessfulRedirect(authorizationRequest,
generateCode(authorizationRequest, authUser)), false, true, false);
} catch (OAuth2Exception e) {
return new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, false), false, true, false);
}
}
private String appendAccessToken(AuthorizationRequest authorizationRequest, OAuth2AccessToken accessToken) {
Map<String, Object> vars = new LinkedHashMap<>();
Map<String, String> keys = new HashMap<>();
if (accessToken == null) {
throw new InvalidRequestException("无法完成implicit授权");
}
vars.put("access_token", accessToken.getValue());
vars.put("token_type", accessToken.getTokenType());
String state = authorizationRequest.getState();
if (state != null) {
vars.put("state", state);
}
Date expiration = accessToken.getExpiration();
if (expiration != null) {
long expires_in = (expiration.getTime() - System.currentTimeMillis()) / 1000;
vars.put("expires_in", expires_in);
}
String originalScope = authorizationRequest.getRequestParameters().get(OAuth2Utils.SCOPE);
if (originalScope == null || !OAuth2Utils.parseParameterList(originalScope).equals(accessToken.getScope())) {
vars.put("scope", OAuth2Utils.formatParameterList(accessToken.getScope()));
}
Map<String, Object> additionalInformation = accessToken.getAdditionalInformation();
for (String key : additionalInformation.keySet()) {
Object value = additionalInformation.get(key);
if (value != null) {
keys.put("extra_" + key, key);
vars.put("extra_" + key, value);
}
}
return append(authorizationRequest.getRedirectUri(), vars, keys, true);
}
private String generateCode(AuthorizationRequest authorizationRequest, Authentication authentication)
throws AuthenticationException {
try {
OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest);
OAuth2Authentication combinedAuth = new OAuth2Authentication(storedOAuth2Request, authentication);
String code = authorizationCodeServices.createAuthorizationCode(combinedAuth);
return code;
} catch (OAuth2Exception e) {
if (authorizationRequest.getState() != null) {
e.addAdditionalInformation("state", authorizationRequest.getState());
}
throw e;
}
}
private String getSuccessfulRedirect(AuthorizationRequest authorizationRequest, String authorizationCode) {
if (authorizationCode == null) {
throw new IllegalStateException("在当前请求范围中找不到授权码");
}
Map<String, String> query = new LinkedHashMap<>();
query.put("code", authorizationCode);
String state = authorizationRequest.getState();
if (state != null) {
query.put("state", state);
}
return append(authorizationRequest.getRedirectUri(), query, false);
}
private String getUnsuccessfulRedirect(AuthorizationRequest authorizationRequest, OAuth2Exception failure,
boolean fragment) {
if (authorizationRequest == null || authorizationRequest.getRedirectUri() == null) {
throw new UnapprovedClientAuthenticationException("授权失败并且没有重定向URI。", failure);
}
Map<String, String> query = new LinkedHashMap<>();
query.put("error", failure.getOAuth2ErrorCode());
query.put("error_description", failure.getMessage());
if (authorizationRequest.getState() != null) {
query.put("state", authorizationRequest.getState());
}
if (failure.getAdditionalInformation() != null) {
for (Map.Entry<String, String> additionalInfo : failure.getAdditionalInformation().entrySet()) {
query.put(additionalInfo.getKey(), additionalInfo.getValue());
}
}
return append(authorizationRequest.getRedirectUri(), query, fragment);
}
private String append(String base, Map<String, ?> query, boolean fragment) {
return append(base, query, null, fragment);
}
private String append(String base, Map<String, ?> query, Map<String, String> keys, boolean fragment) {
UriComponentsBuilder template = UriComponentsBuilder.newInstance();
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base);
URI redirectUri;
try {
redirectUri = builder.build(true).toUri();
} catch (Exception e) {
redirectUri = builder.build().toUri();
builder = UriComponentsBuilder.fromUri(redirectUri);
}
template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost())
.userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath());
if (fragment) {
StringBuilder values = new StringBuilder();
if (redirectUri.getFragment() != null) {
String append = redirectUri.getFragment();
values.append(append);
}
for (String key : query.keySet()) {
if (values.length() > 0) {
values.append("&");
}
String name = key;
if (keys != null && keys.containsKey(key)) {
name = keys.get(key);
}
values.append(name + "={" + key + "}");
}
if (values.length() > 0) {
template.fragment(values.toString());
}
UriComponents encoded = template.build().expand(query).encode();
builder.fragment(encoded.getFragment());
} else {
for (String key : query.keySet()) {
String name = key;
if (keys != null && keys.containsKey(key)) {
name = keys.get(key);
}
template.queryParam(name, "{" + key + "}");
}
template.fragment(redirectUri.getFragment());
UriComponents encoded = template.build().expand(query).encode();
builder.query(encoded.getQuery());
}
return builder.build().toUriString();
}
public void setUserApprovalPage(String userApprovalPage) {
this.userApprovalPage = userApprovalPage;
}
public void setAuthorizationCodeServices(AuthorizationCodeServices authorizationCodeServices) {
this.authorizationCodeServices = authorizationCodeServices;
}
public void setRedirectResolver(RedirectResolver redirectResolver) {
this.redirectResolver = redirectResolver;
}
public void setUserApprovalHandler(UserApprovalHandler userApprovalHandler) {
this.userApprovalHandler = userApprovalHandler;
}
public void setOAuth2RequestValidator(OAuth2RequestValidator oauth2RequestValidator) {
this.oauth2RequestValidator = oauth2RequestValidator;
}
@SuppressWarnings("deprecation")
public void setImplicitGrantService(
org.springframework.security.oauth2.provider.implicit.ImplicitGrantService implicitGrantService) {
}
@ExceptionHandler(ClientRegistrationException.class)
public ModelAndView handleClientRegistrationException(Exception e, ServletWebRequest webRequest) throws Exception {
logger.info("Handling ClientRegistrationException error: " + e.getMessage());
return handleException(new OAuth2ClientBadClientCredentialsException(e.getMessage()), webRequest);
}
@ExceptionHandler(OAuth2Exception.class)
public ModelAndView handleOAuth2Exception(OAuth2Exception e, ServletWebRequest webRequest) throws Exception {
logger.info("Handling OAuth2 error: " + e.getSummary());
return handleException(e, webRequest);
}
@ExceptionHandler(HttpSessionRequiredException.class)
public ModelAndView handleHttpSessionRequiredException(HttpSessionRequiredException e, ServletWebRequest webRequest)
throws Exception {
logger.info("Handling Session required error: " + e.getMessage());
return handleException(new AccessDeniedException("无法从会话获取授权请求。", e),
webRequest);
}
private ModelAndView handleException(Exception e, ServletWebRequest webRequest) throws Exception {
ResponseEntity<OAuth2Exception> translate = getExceptionTranslator().translate(e);
webRequest.getResponse().setStatus(translate.getStatusCode().value());
if (e instanceof ClientAuthenticationException || e instanceof RedirectMismatchException) {
return new ModelAndView(errorPage, Collections.singletonMap("error", translate.getBody().getMessage()));
}
AuthorizationRequest authorizationRequest = null;
try {
authorizationRequest = getAuthorizationRequestForError(webRequest);
String requestedRedirectParam = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
String requestedRedirect = redirectResolver.resolveRedirect(requestedRedirectParam,
getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId()));
authorizationRequest.setRedirectUri(requestedRedirect);
String redirect = getUnsuccessfulRedirect(authorizationRequest, translate.getBody(), authorizationRequest
.getResponseTypes().contains("token"));
return new ModelAndView(new RedirectView(redirect, false, true, false));
} catch (OAuth2Exception ex) {
return new ModelAndView(errorPage, Collections.singletonMap("error", translate.getBody().getMessage()));
}
}
private AuthorizationRequest getAuthorizationRequestForError(ServletWebRequest webRequest) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) sessionAttributeStore.retrieveAttribute(
webRequest, "authorizationRequest");
if (authorizationRequest != null) {
return authorizationRequest;
}
Map<String, String> parameters = new HashMap<>();
Map<String, String[]> map = webRequest.getParameterMap();
for (String key : map.keySet()) {
String[] values = map.get(key);
if (values != null && values.length > 0) {
parameters.put(key, values[0]);
}
}
try {
return getOAuth2RequestFactory().createAuthorizationRequest(parameters);
} catch (Exception e) {
return getDefaultOAuth2RequestFactory().createAuthorizationRequest(parameters);
}
}
}

View File

@ -0,0 +1,91 @@
package ink.wgink.login.oauth2.server.endpoint;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Map;
/**
* When you feel like quitting. Think about why you started
* 当你想要放弃的时候想想当初你为何开始
*
* @ClassName: OauthClientCheckTokenEndpoint
* @Description: Oauth客户端token校验
* @Author: WangGeng
* @Date: 2020/7/24 11:10 上午
* @Version: 1.0
**/
@Controller
public class OAuth2ClientCheckTokenEndpoint {
private ResourceServerTokenServices resourceServerTokenServices;
private AccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
protected final Log logger = LogFactory.getLog(getClass());
private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator();
public OAuth2ClientCheckTokenEndpoint(ResourceServerTokenServices resourceServerTokenServices) {
this.resourceServerTokenServices = resourceServerTokenServices;
}
/**
* @param exceptionTranslator the exception translator to set
*/
public void setExceptionTranslator(WebResponseExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator;
}
/**
* @param accessTokenConverter the accessTokenConverter to set
*/
public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) {
this.accessTokenConverter = accessTokenConverter;
}
@RequestMapping(value = "/oauth2_client/check_token")
@ResponseBody
public Map<String, ?> checkToken(@RequestParam("token") String value) {
OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
if (token == null) {
throw new InvalidTokenException("Token was not recognised");
}
if (token.isExpired()) {
throw new InvalidTokenException("Token has expired");
}
OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());
Map<String, Object> response = (Map<String, Object>) accessTokenConverter.convertAccessToken(token, authentication);
response.put("active", true);
return response;
}
@ExceptionHandler(InvalidTokenException.class)
public ResponseEntity<OAuth2Exception> handleException(Exception e) throws Exception {
logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
@SuppressWarnings("serial")
InvalidTokenException e400 = new InvalidTokenException(e.getMessage()) {
@Override
public int getHttpErrorCode() {
return 400;
}
};
return exceptionTranslator.translate(e400);
}
}

View File

@ -0,0 +1,177 @@
package ink.wgink.login.oauth2.server.endpoint;
import ink.wgink.login.oauth2.server.exceptions.OAuth2ClientBadClientCredentialsException;
import ink.wgink.login.oauth2.server.service.impl.OAuth2ClientDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.*;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.endpoint.AbstractEndpoint;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.security.Principal;
import java.util.*;
/**
* When you feel like quitting. Think about why you started
* 当你想要放弃的时候想想当初你为何开始
*
* @ClassName: OauthClientTokenEndpoint
* @Description: 客户端Token端口
* @Author: WangGeng
* @Date: 2020/7/24 10:06 上午
* @Version: 1.0
**/
@Controller
public class OAuth2ClientTokenEndpoint extends AbstractEndpoint {
private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator();
private Set<HttpMethod> allowedRequestMethods = new HashSet<HttpMethod>(Arrays.asList(HttpMethod.POST));
@Autowired
private TokenGranter tokenGranter;
@Autowired
private OAuth2ClientDetailsServiceImpl oauthClientDetailsService;
@Override
public void afterPropertiesSet() throws Exception {
setTokenGranter(tokenGranter);
setClientDetailsService(oauthClientDetailsService);
super.afterPropertiesSet();
}
@RequestMapping(value = "/oauth2_client/token", method = RequestMethod.GET)
public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal,
@RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!allowedRequestMethods.contains(HttpMethod.GET)) {
throw new HttpRequestMethodNotSupportedException("GET");
}
return postAccessToken(principal, parameters);
}
@RequestMapping(value = "/oauth2_client/token", method = RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal,
@RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException("无客户端身份验证。尝试添加适当的身份验证筛选器。");
}
String clientId = getClientId(principal);
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
if (clientId != null && !clientId.equals("")) {
if (!clientId.equals(tokenRequest.getClientId())) {
throw new InvalidClientException("给定的客户端ID与经过身份验证的客户端不匹配。");
}
}
if (authenticatedClient != null) {
oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
}
if (!StringUtils.hasText(tokenRequest.getGrantType())) {
throw new InvalidRequestException("缺少授权类型。");
}
if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("令牌终结点不支持Implicit授予类型");
}
if (isAuthCodeRequest(parameters)) {
if (!tokenRequest.getScope().isEmpty()) {
logger.debug("Clearing scope of incoming token request");
tokenRequest.setScope(Collections.<String>emptySet());
}
}
if (isRefreshTokenRequest(parameters)) {
tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
}
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("不支持的授权类型: " + tokenRequest.getGrantType());
}
return getResponse(token);
}
/**
* @param principal the currently authentication principal
* @return a client id if there is one in the principal
*/
protected String getClientId(Principal principal) {
Authentication client = (Authentication) principal;
if (!client.isAuthenticated()) {
throw new InsufficientAuthenticationException("客户端未经过身份验证。");
}
String clientId = client.getName();
if (client instanceof OAuth2Authentication) {
clientId = ((OAuth2Authentication) client).getOAuth2Request().getClientId();
}
return clientId;
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<OAuth2Exception> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) throws Exception {
if (logger.isErrorEnabled()) {
logger.error("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage(), e);
}
return getExceptionTranslator().translate(e);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<OAuth2Exception> handleException(Exception e) throws Exception {
if (logger.isErrorEnabled()) {
logger.error("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage(), e);
}
return getExceptionTranslator().translate(e);
}
@ExceptionHandler(ClientRegistrationException.class)
public ResponseEntity<OAuth2Exception> handleClientRegistrationException(Exception e) throws Exception {
if (logger.isErrorEnabled()) {
logger.error("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage(), e);
}
return getExceptionTranslator().translate(new OAuth2ClientBadClientCredentialsException(e.getMessage()));
}
@ExceptionHandler(OAuth2Exception.class)
public ResponseEntity<OAuth2Exception> handleException(OAuth2Exception e) throws Exception {
if (logger.isErrorEnabled()) {
logger.error("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage(), e);
}
return getExceptionTranslator().translate(e);
}
private ResponseEntity<OAuth2AccessToken> getResponse(OAuth2AccessToken accessToken) {
HttpHeaders headers = new HttpHeaders();
headers.set("Cache-Control", "no-store");
headers.set("Pragma", "no-cache");
return new ResponseEntity<OAuth2AccessToken>(accessToken, headers, HttpStatus.OK);
}
private boolean isRefreshTokenRequest(Map<String, String> parameters) {
return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null;
}
private boolean isAuthCodeRequest(Map<String, String> parameters) {
return "authorization_code".equals(parameters.get("grant_type")) && parameters.get("code") != null;
}
public void setOAuth2RequestValidator(OAuth2RequestValidator oAuth2RequestValidator) {
this.oAuth2RequestValidator = oAuth2RequestValidator;
}
public void setAllowedRequestMethods(Set<HttpMethod> allowedRequestMethods) {
this.allowedRequestMethods = allowedRequestMethods;
}
}

View File

@ -0,0 +1,55 @@
package ink.wgink.login.oauth2.server.endpoint;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.security.Principal;
import java.util.Map;
/**
* When you feel like quitting. Think about why you started
* 当你想要放弃的时候想想当初你为何开始
*
* @ClassName: OauthClientTokenKeyEndpoint
* @Description: Oauth客户端TokenKey端口
* @Author: WangGeng
* @Date: 2020/7/26 9:59 上午
* @Version: 1.0
**/
@Controller
public class OAuth2ClientTokenKeyEndpoint {
protected final Log logger = LogFactory.getLog(getClass());
@Resource(name = "jwtAccessTokenConverter")
private JwtAccessTokenConverter converter;
/**
* Get the verification key for the token signatures. The principal has to
* be provided only if the key is secret
* (shared not public).
*
* @param principal the currently authenticated user if there is one
* @return the key used to verify tokens
*/
@RequestMapping(value = "/oauth2_client/token_key", method = RequestMethod.GET)
@ResponseBody
public Map<String, String> getKey(Principal principal) {
boolean flag = (principal == null || principal instanceof AnonymousAuthenticationToken) && !converter.isPublic();
if (flag) {
throw new AccessDeniedException("您需要进行身份验证才能查看共享密钥");
}
Map<String, String> result = converter.getKey();
return result;
}
}

View File

@ -1,7 +1,6 @@
package ink.wgink.login.oauth2.server.pojo.pos;
import ink.wgink.login.oauth2.server.pojo.dtos.OAuth2ClientSimpleDTO;
import io.swagger.annotations.ApiModel;
import java.io.Serializable;
/**
* @ClassName: OauthClientDTO
@ -10,9 +9,12 @@ import io.swagger.annotations.ApiModel;
* @Date: 2019/1/8 7:43 PM
* @Version: 1.0
**/
@ApiModel
public class OAuth2ClientPO extends OAuth2ClientSimpleDTO {
public class OAuth2ClientPO implements Serializable{
private static final long serialVersionUID = 2201386758342553263L;
private String clientId;
private String clientName;
private String webServerRedirectUri;
private String resourceIds;
private String clientSecret;
private String scope;
@ -35,6 +37,30 @@ public class OAuth2ClientPO extends OAuth2ClientSimpleDTO {
private String modifier;
private Integer isDelete;
public String getClientId() {
return clientId == null ? "" : clientId.trim();
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientName() {
return clientName == null ? "" : clientName.trim();
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public String getWebServerRedirectUri() {
return webServerRedirectUri == null ? "" : webServerRedirectUri.trim();
}
public void setWebServerRedirectUri(String webServerRedirectUri) {
this.webServerRedirectUri = webServerRedirectUri;
}
public String getResourceIds() {
return resourceIds == null ? "" : resourceIds.trim();
}
@ -163,12 +189,10 @@ public class OAuth2ClientPO extends OAuth2ClientSimpleDTO {
this.systemIcon = systemIcon;
}
@Override
public String getGmtCreate() {
return gmtCreate == null ? "" : gmtCreate.trim();
}
@Override
public void setGmtCreate(String gmtCreate) {
this.gmtCreate = gmtCreate;
}

View File

@ -22,7 +22,7 @@ public interface IOAuth2ClientService {
/**
* 客户端加密规则
*/
String OAUTH_CLIENT_RULE = "WGINK_OAUTH2_CLIENT";
String OAUTH_CLIENT_RULE = "WGINK_oauth2";
/**
* 正常环境
*/

View File

@ -19,9 +19,8 @@ import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
/**
* When you feel like quitting. Think about why you started
@ -35,8 +34,8 @@ import java.util.Set;
**/
@Primary
@Component
public class Oauth2ClientDetailsServiceImpl implements ClientDetailsService {
private static final Logger LOG = LoggerFactory.getLogger(Oauth2ClientDetailsServiceImpl.class);
public class OAuth2ClientDetailsServiceImpl implements ClientDetailsService {
private static final Logger LOG = LoggerFactory.getLogger(OAuth2ClientDetailsServiceImpl.class);
@Autowired
private IOAuth2ClientService oAuth2ClientService;
@ -72,7 +71,7 @@ public class Oauth2ClientDetailsServiceImpl implements ClientDetailsService {
oAuth2ClientPO.getScope(),
oAuth2ClientPO.getAuthorizedGrantTypes(),
oAuth2ClientPO.getAuthorities());
clientDetails.setAutoApproveScopes(Arrays.asList(oAuth2ClientPO.getAutoapprove().split(",")));
clientDetails.setClientSecret(oAuth2ClientPO.getClientSecret());
clientDetails.setAccessTokenValiditySeconds(oAuth2ClientPO.getAccessTokenValidity());
clientDetails.setRefreshTokenValiditySeconds(oAuth2ClientPO.getRefreshTokenValidity());
@ -91,14 +90,6 @@ public class Oauth2ClientDetailsServiceImpl implements ClientDetailsService {
if (!StringUtils.isBlank(oAuth2ClientPO.getScope())) {
clientDetails.setScope(org.springframework.util.StringUtils.commaDelimitedListToSet(oAuth2ClientPO.getScope()));
}
if (clientDetails.isAutoApprove(oAuth2ClientPO.getAutoapprove())) {
Set<String> autoApproveScopesSet = new HashSet<>();
autoApproveScopesSet.add("true");
clientDetails.setAutoApproveScopes(autoApproveScopesSet);
} else {
clientDetails.setAutoApproveScopes(clientDetails.getScope());
}
return clientDetails;
}

View File

@ -32,7 +32,7 @@ import java.util.UUID;
* @Version: 1.0
**/
@Component
public class Oauth2ClientTokenServiceImpl implements AuthorizationServerTokenServices, ResourceServerTokenServices, ConsumerTokenServices, InitializingBean {
public class OAuth2ClientTokenServiceImpl implements AuthorizationServerTokenServices, ResourceServerTokenServices, ConsumerTokenServices, InitializingBean {
private int refreshTokenValiditySeconds = 7200;
private int accessTokenValiditySeconds = 7200;
@ -46,7 +46,7 @@ public class Oauth2ClientTokenServiceImpl implements AuthorizationServerTokenSer
private TokenEnhancer accessTokenEnhancer;
private AuthenticationManager authenticationManager;
public Oauth2ClientTokenServiceImpl() {
public OAuth2ClientTokenServiceImpl() {
}
@Override

View File

@ -53,10 +53,10 @@
<result property="systemSummary" column="system_summary"/>
<result property="systemIcon" column="system_icon"/>
<result property="gmtCreate" column="gmt_create"/>
<result property="creator" column="gmt_create"/>
<result property="creator" column="creator"/>
<result property="gmtModified" column="gmt_create"/>
<result property="modifier" column="gmt_create"/>
<result property="isDelete" column="gmt_create"/>
<result property="modifier" column="modifier"/>
<result property="isDelete" column="is_delete"/>
</resultMap>
<!-- 建表 -->
@ -82,10 +82,10 @@
`system_summary` text COMMENT '系统介绍',
`system_icon` varchar(255) DEFAULT NULL COMMENT '系统图标',
`gmt_create` datetime DEFAULT NULL,
`creator` bigint(20) DEFAULT NULL,
`creator` char(36) DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
`modifier` bigint(20) DEFAULT NULL,
`is_delete` int(2) DEFAULT '0',
`modifier` char(36) DEFAULT NULL,
`is_delete` int(1) DEFAULT '0',
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
</update>

View File

@ -204,7 +204,7 @@
return;
}
top.restAjax.get(top.restAjax.path('api/file/listfilebyfileid', []), {
top.restAjax.get(top.restAjax.path('api/file/list', []), {
ids: ids
}, null, function(code, data) {
refreshDownloadTemplet(fileName, data);
@ -285,7 +285,7 @@
// 初始化
function initData() {
var loadLayerIndex;
top.restAjax.get(top.restAjax.path('api/oauthclient/getinitclient', []), {}, null, function(code, data) {
top.restAjax.get(top.restAjax.path('api/oauth2client/get-init', []), {}, null, function(code, data) {
form.val('dataForm', {
clientId: data.clientId,
clientSecret: data.clientSecret,
@ -310,7 +310,7 @@
top.dialog.confirm(top.dataMessage.commit, function(index) {
top.dialog.close(index);
var loadLayerIndex;
top.restAjax.post(top.restAjax.path('api/oauthclient/saveoauthclient', []), formData.field, null, function(code, data) {
top.restAjax.post(top.restAjax.path('api/oauth2client/save', []), formData.field, null, function(code, data) {
var layerIndex = top.dialog.msg(top.dataMessage.commitSuccess, {
time: 0,
btn: [top.dataMessage.button.yes, top.dataMessage.button.no],

View File

@ -205,7 +205,7 @@
return;
}
top.restAjax.get(top.restAjax.path('api/file/listfilebyfileid', []), {
top.restAjax.get(top.restAjax.path('api/file/list', []), {
ids: ids
}, null, function(code, data) {
refreshDownloadTemplet(fileName, data);
@ -286,7 +286,7 @@
// 初始化
function initData() {
var loadLayerIndex;
top.restAjax.get(top.restAjax.path('api/oauthclient/getoauthclient/{clientId}', [clientId]), {}, null, function(code, data) {
top.restAjax.get(top.restAjax.path('api/oauth2client/get/{clientId}', [clientId]), {}, null, function(code, data) {
form.val('dataForm', {
clientName: data.clientName,
clientId: data.clientId,
@ -330,7 +330,7 @@
top.dialog.confirm(top.dataMessage.commit, function(index) {
top.dialog.close(index);
var loadLayerIndex;
top.restAjax.put(top.restAjax.path('api/oauthclient/updateoauthclient/{clientId}', [clientId]), formData.field, null, function(code, data) {
top.restAjax.put(top.restAjax.path('api/oauth2client/update/{clientId}', [clientId]), formData.field, null, function(code, data) {
var layerIndex = top.dialog.msg(top.dataMessage.commitSuccess, {
time: 0,
btn: [top.dataMessage.button.yes, top.dataMessage.button.no],

12
pom.xml
View File

@ -44,7 +44,7 @@
<properties>
<spring.version>5.2.8.RELEASE</spring.version>
<spring-security.version>5.5.2</spring-security.version>
<spring-security.version>5.1.3.RELEASE</spring-security.version>
<spring-boot.version>2.3.3.RELEASE</spring-boot.version>
<fastjson.version>1.2.24</fastjson.version>
<json.version>20210307</json.version>
@ -153,16 +153,6 @@
<artifactId>spring-security-web</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-core</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>${spring-security.version}</version>
</dependency>
<!-- Spring end -->
<!-- springboot start -->