??目前每家企業或者平臺都存在不止一套系統,由于歷史原因每套系統采購于不同廠商,所以系統間都是相互獨立的,都有自己的用戶鑒權認證體系,當用戶進行登錄系統時,不得不記住每套系統的用戶名密碼,同時,管理員也需要為同一個用戶設置多套系統登錄賬號,這對系統的使用者來說顯然是不方便的。我們期望的是如果存在多個系統,只需要登錄一次就可以訪問多個系統,只需要在其中一個系統執行注銷登錄操作,則所有的系統都注銷登錄,無需重復操作,這就是單點登錄(Single Sign On 簡稱SSO)系統實現的功能。
??單點登錄是系統功能的定義,而實現單點登錄功能,目前開源且流行的有CAS和OAuth2兩種方式,過去我們用的最多的是CAS,現在隨著SpringCloud的流行,更多人選擇使用SpringSecurity提供的OAuth2認證授權服務器實現單點登錄功能。
??OAuth2是一種授權協議的標準,任何人都可以基于這個標準開發Oauth2授權服務器,現在百度開放平臺、騰訊開放平臺等大部分的開放平臺都是基于OAuth2協議實現, OAuth2.0定義了四種授權類型,最新版OAuth2.1協議定義了七種授權類型,其中有兩種因安全問題已不再建議使用:
??通過SpringSecurity官網可知,通過長期的對OAuth2的支持,以及對實際業務的情景考慮,大多數的系統都不需要授權服務器,所以,Spring官方不再推薦使用spring-security-oauth2,SpringSecurity逐漸將spring-security-oauth2中的OAuth2登錄、客戶端、資源服務器等功能抽取出來,集成在SpringSecurity中,并單獨新建spring-authorization-server項目實現授權服務器功能。
??目前我們了解最多的是Spring Security OAuth對OAuth2協議的實現和支持,這里需要區分Spring Security OAuth和Spring Security是兩個項目,過去OAth2相關功能都在Spring Security OAuth項目中實現,但是自SpringSecurity5.X開始,SpringSecurity項目開始逐漸增加Spring Security OAuth中的功能,自SpringSecurity5.2開始,添加了OAuth 2.0 登錄, 客戶端, 資源服務器的功能。但授權服務器的功能,并不打算集成在SpringSecurity項目中,而是新建了spring-authorization-server項目作為單獨的授權服務器:詳細介紹。spring-security實現的是OAuth2.1協議,spring-security-oauth2實現的是OAuth2.0協議。
??Spring未來的計劃是將 Spring Security OAuth 中當前的所有功能構建到 Spring Security 5.x 中。 在 Spring Security 達到與 Spring Security OAuth 的功能對等之后,他們將繼續支持錯誤和安全修復至少一年。
??因spring-authorization-server目前最新發布版本0.2.3,部分功能仍在不斷地修復和完善,還不足以應用到實際生產環境中,所以,我們目前使用spring-security-oauth2作為授權服務器,待后續spring-authorization-server發布穩定版本后,再進行遷移升級。
??在GitEgg微服務框架中,gitegg-oauth已經引入了spring-security-oauth2,代碼中使用了了Oauth2的密碼授權和刷新令牌授權,并且自定義擴展了【短信驗證碼授權類型】和【圖形驗證碼授權】,這其實是密碼授權的擴展授權類型。
??目前,基本上所有的SpringCloud微服務授權方式都是使用的OAuth2密碼授權模式獲取token,可能你會有疑惑,為什么上面最新的Oauth2協議已經不建議甚至是禁止使用密碼授權類型了,而我們GitEgg框架的系統管理界面還要使用密碼授權模式來獲取token?因為不建議使用密碼授權類型的原因是第三方客戶端會收集用戶名密碼,存在安全風險。而在我們這里,我們的客戶端是自有系統管理界面,不是第三方客戶端,所有的用戶名密碼都是我們自有系統的用戶名密碼,只要做好系統安全防護,就可最大限度的避免用戶名密碼泄露給第三方的風險。
spring-security-oauth2單點登錄
??當我們的gitegg-oauth作為授權服務器使用時,我們希望定制自己的登錄頁等信息,下面我們自定義登錄、主頁、錯誤提示頁、找回密碼頁。其他需要的頁面可以自己定義,比如授權確認頁,我們此處業務不需要用戶二次確認,所以這里沒有自定義此頁面。
<!--thymeleaf 模板引擎 渲染單點登錄服務器頁面-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
/**
* 單點登錄-登錄頁
* @return
*/
@GetMapping("/login") public String login() {
return "login";
}
/**
* 單點登錄-首頁:當直接訪問單點登錄系統成功后進入的頁面。從客戶端系統進入的,直接返回到客戶端頁面
* @return
*/
@GetMapping("/index") public String index() {
return "index";
}
/**
* 單點登錄-錯誤頁
* @return
*/
@GetMapping("/error") public String error() {
return "error";
}
/**
* 單點登錄-找回密碼頁
* @return
*/
@GetMapping("/find/pwd") public String findPwd() {
return "findpwd";
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="description" content="統一身份認證平臺">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>統一身份認證平臺</title>
<link rel="shortcut icon" th:href="@{/gitegg-oauth/favicon.ico}"/>
<link rel="bookmark" th:href="@{/gitegg-oauth/favicon.ico}"/>
<link type="text/css" rel="stylesheet" th:href="@{/gitegg-oauth/assets/bootstrap-4.3.1-dist/css/bootstrap.min.css}">
<link type="text/css" rel="stylesheet" th:href="@{/gitegg-oauth/assets/bootstrap-validator-0.5.3/css/bootstrapValidator.css}">
<link type="text/css" rel="stylesheet" th:href="@{/gitegg-oauth/assets/css/font-awesome.min.css}">
<link type="text/css" rel="stylesheet" th:href="@{/gitegg-oauth/assets/css/login.css}">
<!--[if IE]>
<script type="text/javascript" th:src="@{/gitegg-oauth/assets/js/html5shiv.min.js}"></script>
<![endif]-->
</head>
<body>
<div class="htmleaf-container">
<div class="form-bg">
<div class="container">
<div class="row login_wrap">
<div class="login_left">
<span class="circle">
<!-- <span></span>
<span></span> -->
<img th:src="@{/gitegg-oauth/assets/images/logo.svg}" class="logo" alt="logo">
</span>
<span class="star">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</span>
<span class="fly_star">
<span></span>
<span></span>
</span>
<p id="title">
GitEgg Cloud 統一身份認證平臺
</p>
</div>
<div class="login_right">
<div class="title cf">
<ul class="title-list fr cf ">
<li class="on">賬號密碼登錄</li>
<li>驗證碼登錄</li>
<p></p>
</ul>
</div>
<div class="login-form-container account-login">
<form class="form-horizontal account-form" th:action="@{/gitegg-oauth/login}" method="post">
<input type="hidden" class="form-control" name="client_id" value="gitegg-admin">
<input id="user_type" type="hidden" class="form-control" name="type" value="user">
<input id="user_mobileType" type="hidden" class="form-control" name="mobile" value="0">
<div class="input-wrapper input-account-wrapper form-group">
<div class="input-icon-wrapper">
<i class="input-icon">
<svg t="1646301169630" class="icon" viewBox="64 64 896 896" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8796" width="1.2em" height="1.2em" fill="currentColor"><path d="M858.5 763.6c-18.9-44.8-46.1-85-80.6-119.5-34.5-34.5-74.7-61.6-119.5-80.6-0.4-0.2-0.8-0.3-1.2-0.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-0.4 0.2-0.8 0.3-1.2 0.5-44.8 18.9-85 46-119.5 80.6-34.5 34.5-61.6 74.7-80.6 119.5C146.9 807.5 137 854 136 901.8c-0.1 4.5 3.5 8.2 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c0.1 4.4 3.6 7.8 8 7.8h60c4.5 0 8.1-3.7 8-8.2-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z" p-id="8797"></path></svg>
</i>
</div>
<input type="text" class="input" name="username" placeholder="請輸入您的賬號">
</div>
<div class="input-wrapper input-psw-wrapper form-group">
<div class="input-icon-wrapper">
<i class="input-icon">
<svg t="1646302713220" class="icon" viewBox="64 64 896 896" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8931" width="1.2em" height="1.2em" fill="currentColor"><path d="M832 464h-68V240c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32zM332 240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v224H332V240z m460 600H232V536h560v304z" p-id="8932"></path><path d="M484 701v53c0 4.4 3.6 8 8 8h40c4.4 0 8-3.6 8-8v-53c12.1-8.7 20-22.9 20-39 0-26.5-21.5-48-48-48s-48 21.5-48 48c0 16.1 7.9 30.3 20 39z" p-id="8933"></path></svg>
</i>
</div>
<input id="password" type="password" class="input" name="password" placeholder="請輸入您的密碼">
</div>
<div id="account-err" class="err-msg" style="width: 100%; text-align: center;"></div>
<button type="submit" class="login-btn" id="loginSubmit">立即登錄</button>
<div class="forget" id="forget">忘記密碼?</div>
</form>
</div>
<div class="login-form-container mobile-login" style="display: none;">
<form class="form-horizontal mobile-form" th:action="@{/gitegg-oauth/phoneLogin}" method="post">
<input id="tenantId" type="hidden" class="form-control" name="tenant_id" value="0">
<input id="type" type="hidden" class="form-control" name="type" value="phone">
<input id="mobileType" type="hidden" class="form-control" name="mobile" value="0">
<input id="smsId" type="hidden" class="form-control" name="smsId">
<div class="input-wrapper input-account-wrapper form-group input-phone-wrapper">
<div class="input-icon-wrapper">
<i class="input-icon">
<svg t="1646302822533" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9067" width="1.2em" height="1.2em" fill="currentColor"><path d="M744 62H280c-35.3 0-64 28.7-64 64v768c0 35.3 28.7 64 64 64h464c35.3 0 64-28.7 64-64V126c0-35.3-28.7-64-64-64z m-8 824H288V134h448v752z" p-id="9068"></path><path d="M512 784m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" p-id="9069"></path></svg>
</i>
</div>
<input id="phone" type="text" class="input" name="phone" maxlength="11" placeholder="請輸入手機號">
</div>
<div class="code-form form-group sms-code-wrapper">
<div class="input-wrapper input-sms-wrapper">
<div class="input-icon-wrapper">
<i class="input-icon">
<svg t="1646302879723" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9203" width="1.2em" height="1.2em" fill="currentColor"><path d="M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32z m-40 110.8V792H136V270.8l-27.6-21.5 39.3-50.5 42.8 33.3h643.1l42.8-33.3 39.3 50.5-27.7 21.5z" p-id="9204"></path><path d="M833.6 232L512 482 190.4 232l-42.8-33.3-39.3 50.5 27.6 21.5 341.6 265.6c20.2 15.7 48.5 15.7 68.7 0L888 270.8l27.6-21.5-39.3-50.5-42.7 33.2z" p-id="9205"></path></svg>
</i>
</div>
<input id="code" type="text" class="input-code" name="code" maxlength="6" placeholder="請輸入驗證碼">
</div>
<div class="input-code-wrapper">
<a id="sendBtn" href="javascript:sendCode();">獲取驗證碼</a>
</div>
</div>
<div id="mobile-err" class="err-msg" style="width: 100%; text-align: center;"></div>
<button type="submit" class="login-btn" id="loginSubmitByCode">立即登錄</button>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="related">
Copyrights ? 2021 GitEgg All Rights Reserved.
</div>
</div>
<script type="text/javascript" th:src="@{/gitegg-oauth/assets/js/jquery-2.1.4.min.js}"></script>
<script type="text/javascript" th:src="@{/gitegg-oauth/assets/bootstrap-4.3.1-dist/js/bootstrap.min.js}"></script>
<script type="text/javascript" th:src="@{/gitegg-oauth/assets/bootstrap-validator-0.5.3/js/bootstrapValidator.js}"></script>
<script type="text/javascript" th:src="@{/gitegg-oauth/assets/js/md5.js}"></script>
<script type="text/javascript" th:src="@{/gitegg-oauth/assets/js/jquery.form.js}"></script>
<script type="text/javascript" th:src="@{/gitegg-oauth/assets/js/login.js}"></script>
</body>
</html>
var countdown=60;
jQuery(function ($) {
countdown=60;
$('.account-form').bootstrapValidator({
message: '輸入錯誤',
feedbackIcons: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
fields: {
username: {
container: '.input-account-wrapper',
message: '輸入錯誤',
validators: {
notEmpty: {
message: '用戶賬號不能為空'
},
stringLength: {
min: 2,
max: 32,
message: '賬號長度范圍2-32個字符。'
},
regexp: {
regexp: /^[a-zA-Z0-9_\.]+$/,
message: '用戶名只能由字母、數字、點和下劃線組成'
}
}
},
password: {
container: '.input-psw-wrapper',
validators: {
notEmpty: {
message: '密碼不能為空'
},
stringLength: {
min: 5,
max: 32,
message: '密碼長度范圍6-32個字符。'
}
}
}
}
});
$('.mobile-form').bootstrapValidator({
message: '輸入錯誤',
feedbackIcons: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
fields: {
phone: {
message: '輸入錯誤',
container: '.input-phone-wrapper',
validators: {
notEmpty: {
message: '手機號不能為空'
},
regexp: {
regexp: /^1\d{10}$/,
message: '手機號格式錯誤'
}
}
},
code: {
container: '.input-sms-wrapper',
validators: {
notEmpty: {
message: '驗證碼不能為空'
},
stringLength: {
min: 6,
max: 6,
message: '驗證碼長度為6位。'
}
}
}
}
});
var options={
beforeSerialize: beforeFormSerialize,
success: formSuccess,//提交成功后執行的回掉函數
error: formError,//提交失敗后執行的回掉函數
headers : {"TenantId" : 0},
clearForm: true,//提交成功后是否清空表單中的字段值
restForm: true,//提交成功后是否充值表單中的字段值,即恢復到頁面加載是的狀態
timeout: 6000//設置請求時間,超過時間后,自動退出請求,單位(毫秒)
}
var mobileOptions={
success: mobileFormSuccess,//提交成功后執行的回掉函數
error: mobileFormError,//提交失敗后執行的回掉函數
headers : {"TenantId" : 0},
clearForm: true,//提交成功后是否清空表單中的字段值
restForm: true,//提交成功后是否充值表單中的字段值,即恢復到頁面加載是的狀態
timeout: 6000//設置請求時間,超過時間后,自動退出請求,單位(毫秒)
}
function beforeFormSerialize(){
$("#account-err").html("");
$("#username").val($.trim($("#username").val()));
$("#password").val($.md5($.trim($("#password").val())));
}
function formSuccess(response){
$(".account-form").data('bootstrapValidator').resetForm();
if (response.success)
{
window.location.href=response.targetUrl;
}
else
{
$("#account-err").html(response.message);
}
}
function formError(response){
$("#account-err").html(response);
}
function mobileFormSuccess(response){
$(".mobile-form").data('bootstrapValidator').resetForm();
if (response.success)
{
window.location.href=response.targetUrl;
}
else
{
$("#mobile-err").html(response.message);
}
}
function mobileFormError(response){
$("#mobile-err").html(response);
}
$(".account-form").ajaxForm(options);
$(".mobile-form").ajaxForm(mobileOptions);
$(".nav-left a").click(function(e){
$(".account-login").show();
$(".mobile-login").hide();
});
$(".nav-right a").click(function(e){
$(".account-login").hide();
$(".mobile-login").show();
});
$("#forget").click(function(e){
window.location.href="/find/pwd";
});
$('.title-list li').click(function(){
var liindex=$('.title-list li').index(this);
$(this).addClass('on').siblings().removeClass('on');
$('.login_right div.login-form-container').eq(liindex).fadeIn(150).siblings('div.login-form-container').hide();
var liWidth=$('.title-list li').width();
if (liindex==0)
{
$('.login_right .title-list p').css("transform","translate3d(0px, 0px, 0px)");
}
else {
$('.login_right .title-list p').css("transform","translate3d("+liWidth+"px, 0px, 0px)");
}
});
});
function sendCode(){
$(".mobile-form").data('bootstrapValidator').validateField('phone');
if(!$(".mobile-form").data('bootstrapValidator').isValidField("phone"))
{
return;
}
if(countdown !=60)
{
return;
}
sendmsg();
var phone=$.trim($("#phone").val());
var tenantId=$("#tenantId").val();
$.ajax({
//請求方式
type : "POST",
//請求的媒體類型
contentType: "application/x-www-form-urlencoded;charset=UTF-8",
dataType: 'json',
//請求地址
url : "/code/sms/login",
//數據,json字符串
data : {
tenantId: tenantId,
phoneNumber: phone,
code: "aliValidateLogin"
},
//請求成功
success : function(result) {
$("#smsId").val(result.data);
},
//請求失敗,包含具體的錯誤信息
error : function(e){
console.log(e);
}
});
};
function sendmsg(){
if(countdown==0){
$("#sendBtn").css("color","#181818");
$("#sendBtn").html("獲取驗證碼");
countdown=60;
return false;
}
else{
$("#sendBtn").css("color","#74777b");
$("#sendBtn").html("重新發送("+countdown+")");
countdown--;
}
setTimeout(function(){
sendmsg();
},1000);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/assets/**", "/css/**", "/images/**");
}
# 以下配置為新增
whiteUrls:
- "/gitegg-oauth/oauth/login"
- "/gitegg-oauth/oauth/find/pwd"
- "/gitegg-oauth/oauth/error"
authUrls:
- "/gitegg-oauth/oauth/index"
whiteUrls:
- "/*/v2/api-docs"
- "/gitegg-oauth/oauth/public_key"
- "/gitegg-oauth/oauth/token_key"
- "/gitegg-oauth/find/pwd"
- "/gitegg-oauth/code/sms/login"
- "/gitegg-oauth/change/password"
- "/gitegg-oauth/error"
- "/gitegg-oauth/oauth/sms/captcha/send"
# 新增OAuth2認證接口,此處網關放行,由認證中心進行認證
tokenUrls:
- "/gitegg-oauth/oauth/token"
package com.gitegg.oauth.filter;
import cn.hutool.core.bean.BeanUtil;
import com.gitegg.oauth.token.PhoneAuthenticationToken;
import com.gitegg.platform.base.constant.AuthConstant;
import com.gitegg.platform.base.domain.GitEggUser;
import com.gitegg.platform.base.result.Result;
import com.gitegg.service.system.client.feign.IUserFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定義登陸
* @author GitEgg
*/
public class GitEggLoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public static final String SPRING_SECURITY_RESTFUL_TYPE_PHONE="phone";
public static final String SPRING_SECURITY_RESTFUL_TYPE_QR="qr";
public static final String SPRING_SECURITY_RESTFUL_TYPE_DEFAULT="user";
// 登陸類型:user:用戶密碼登陸;phone:手機驗證碼登陸;qr:二維碼掃碼登陸
private static final String SPRING_SECURITY_RESTFUL_TYPE_KEY="type";
// 登陸終端:1:移動端登陸,包括微信公眾號、小程序等;0:PC后臺登陸
private static final String SPRING_SECURITY_RESTFUL_MOBILE_KEY="mobile";
private static final String SPRING_SECURITY_RESTFUL_USERNAME_KEY="username";
private static final String SPRING_SECURITY_RESTFUL_PASSWORD_KEY="password";
private static final String SPRING_SECURITY_RESTFUL_PHONE_KEY="phone";
private static final String SPRING_SECURITY_RESTFUL_VERIFY_CODE_KEY="code";
private static final String SPRING_SECURITY_RESTFUL_QR_CODE_KEY="qrCode";
@Autowired
private IUserFeign userFeign;
private boolean postOnly=true;
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (postOnly && !"POST".equals(request.getMethod())) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String type=obtainParameter(request, SPRING_SECURITY_RESTFUL_TYPE_KEY);
String mobile=obtainParameter(request, SPRING_SECURITY_RESTFUL_MOBILE_KEY);
AbstractAuthenticationToken authRequest;
String principal;
String credentials;
// 手機驗證碼登陸
if(SPRING_SECURITY_RESTFUL_TYPE_PHONE.equals(type)){
principal=obtainParameter(request, SPRING_SECURITY_RESTFUL_PHONE_KEY);
credentials=obtainParameter(request, SPRING_SECURITY_RESTFUL_VERIFY_CODE_KEY);
principal=principal.trim();
authRequest=new PhoneAuthenticationToken(principal, credentials);
}
// 賬號密碼登陸
else {
principal=obtainParameter(request, SPRING_SECURITY_RESTFUL_USERNAME_KEY);
credentials=obtainParameter(request, SPRING_SECURITY_RESTFUL_PASSWORD_KEY);
Result<Object> result=userFeign.queryUserByAccount(principal);
if (null !=result && result.isSuccess()) {
GitEggUser gitEggUser=new GitEggUser();
BeanUtil.copyProperties(result.getData(), gitEggUser, false);
if (!StringUtils.isEmpty(gitEggUser.getAccount())) {
principal=gitEggUser.getAccount();
credentials=AuthConstant.BCRYPT + gitEggUser.getAccount() + credentials;
}
}
authRequest=new UsernamePasswordAuthenticationToken(principal, credentials);
}
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private void setDetails(HttpServletRequest request,
AbstractAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
private String obtainParameter(HttpServletRequest request, String parameter) {
String result=request.getParameter(parameter);
return result==null ? "" : result;
}
}
?? spring-security-oauth2提供OAuth2授權服務器的同時也提供了單點登錄客戶端的實現,通用通過幾行注解即可實現單點登錄功能。
1、新建單點登錄客戶端工程,引入oauth2客戶端相關jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
2、新建WebSecurityConfig類,添加@EnableOAuth2Sso注解
@EnableOAuth2Sso
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.csrf().disable();
}
}
3、配置單點登錄服務端相關信息
server:
port: 8080
servlet:
context-path: /ssoclient1
security:
oauth2:
client:
# 配置在授權服務器配置的客戶端id和secret
client-id: ssoclient
client-secret: 123456
# 獲取token的url
access-token-uri: http://127.0.0.1/gitegg-oauth/oauth/token
# 授權服務器的授權地址
user-authorization-uri: http://127.0.0.1/gitegg-oauth/oauth/authorize
resource:
jwt:
# 獲取公鑰的地址,驗證token需使用,系統啟動時會初始化,不會每次驗證都請求
key-uri: http://127.0.0.1/gitegg-oauth/oauth/token_key
1、GitEgg框架中自定義了token返回格式,SpringSecurity獲取token的/oauth/token默認返回的是ResponseEntity,自有系統登錄和單點登錄時需要做轉換處理。
2、Gateway網關鑒權需要的公鑰地址是gitegg-oauth/oauth/public_key,單點登錄客戶端需要公鑰地址
/oauth/token_key,兩者返回的格式不一樣,需注意區分。
3、請求/oauth/tonen和/oauth/token_key時,默認都需要使用Basic認證,也就是請求時需添加client_id和client_security參數。
GitEgg: GitEgg 是一款開源免費的企業級微服務應用開發框架,旨在整合目前主流穩定的開源技術框架,集成常用的最佳項目解決方案,實現可直接使用的微服務快速開發框架。
GitHub - wmz1930/GitEgg: GitEgg 是一款開源免費的企業級微服務應用開發框架,旨在整合目前主流穩定的開源技術框架,集成常用的最佳項目解決方案,實現可直接使用的微服務快速開發框架。
事先安裝好,pycharm
打開File——>Settings——>Projext——>Project Interpriter
點擊加號(圖中紅圈的地方)
點擊紅圈中的按鈕
選中第一條,點擊鉛筆,將原來的鏈接替換為(這里已經替換過了):
https://pypi.tuna.tsinghua.edu.cn/simple/
點擊OK后,輸入requests-html然后回車
選中requests-html后點擊Install Package
等待安裝成功,關閉
實例內容:
從某博主的所有文章爬取想要的內容。
實例背景:
從(https://me.csdn.net/weixin_44286745)博主的所有文章獲取各文章的標題,時間,閱讀量。
from requests_html import HTMLSession
session=HTMLSession()
123
html=session.get("https://me.csdn.net/weixin_44286745").html
12
allBlog=html.xpath("//dl[@class='tab_page_list']")
1
for i in allBlog:
title=i.xpath("dl/dt/h3/a")[0].text
views=i.xpath("//div[@class='tab_page_b_l fl']")[0].text
date=i.xpath("//div[@class='tab_page_b_r fr']")[0].text
print(title +' ' +views +' ' + date )
12345
網頁分析:
完整代碼:
from requests_html import HTMLSession
session=HTMLSession()
html=session.get("https://me.csdn.net/weixin_44286745").html
allBlog=html.xpath("//dl[@class='tab_page_list']")
for i in allBlog:
title=i.xpath("dl/dt/h3/a")[0].text
views=i.xpath("//div[@class='tab_page_b_l fl']")[0].text
date=i.xpath("//div[@class='tab_page_b_r fr']")[0].text
print(title +' ' +views +' ' + date )
1234567891011121314
喜歡編程的小伙伴可以加一下小編的Q群867067945大家一起交流學習,群里也有專業的大神給你解答難題
文來源于黑馬程序員技術論壇:http://bbs.itheima.com/thread-430137-1-2.html
Struts2 的Action中若希望訪問Session對象,可采用兩種方式:
1、從ActionContext中獲取;
2、實現SessionAware接口。
1、從ActionContext中獲取:
import java.util.Map;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class SessionTestAction extends ActionSupport {
public String execute() {
ActionContext actionContext=ActionContext.getContext();
Map session=actionContext.getSession();
session.put("USER_NAME", "Test User");
return SUCCESS;
}
}
import java.util.Map; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; public class SessionTestAction extends ActionSupport { public String execute() { ActionContext actionContext=ActionContext.getContext(); Map session=actionContext.getSession(); session.put("USER_NAME", "Test User"); return SUCCESS; } }
2、實現SessionAware接口:
import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;
import com.opensymphony.xwork2.ActionSupport;
public class SessionTest1Action extends ActionSupport implements SessionAware {
private Map session;
public void setSession(Map session) {
this.session=session;
}
public String execute() {
this.session.put("USER_NAME", "Test User 1");
return SUCCESS;
}
}
import java.util.Map; import org.apache.struts2.interceptor.SessionAware; import com.opensymphony.xwork2.ActionSupport; public class SessionTest1Action extends ActionSupport implements SessionAware { private Map session; public void setSession(Map session) { this.session=session; } public String execute() { this.session.put("USER_NAME", "Test User 1"); return SUCCESS; } }
進一步閱讀Struts2.1.8.1源碼,SessionAware接口的實現方式如下:
struts-default.xml配置:
<interceptors>
…
<interceptor name="servletConfig" class="org.apache.struts2.interceptor.ServletConfigInterceptor"/>
…
</interceptors>
<interceptor-stack name="defaultStack">
…
<interceptor-ref name="servletConfig"/>
…
</interceptor-stack>
<interceptors> … <interceptor name="servletConfig" class="org.apache.struts2.interceptor.ServletConfigInterceptor"/> … </interceptors> <interceptor-stack name="defaultStack"> … <interceptor-ref name="servletConfig"/> … </interceptor-stack>
打開ServletConfigInterceptor.java源碼:
public String intercept(ActionInvocation invocation) throws Exception {
final Object action=invocation.getAction();
final ActionContext context=invocation.getInvocationContext();
…
if (action instanceof SessionAware) {
((SessionAware) action)。setSession(context.getSession());
}
…
return invocation.invoke();
}
public String intercept(ActionInvocation invocation) throws Exception { final Object action=invocation.getAction(); final ActionContext context=invocation.getInvocationContext(); … if (action instanceof SessionAware) { ((SessionAware) action)。setSession(context.getSession()); } … return invocation.invoke(); }
即在攔截器處理過程中發現目標Action實現了SessionAware接口,便會調用Action中已經實現的setSession(…) 方法,將ActionContext中包裝的Session注入目標Action中。目標Action也就可以進一步對Session進行操作了。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。