Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
??目前每家企業(yè)或者平臺(tái)都存在不止一套系統(tǒng),由于歷史原因每套系統(tǒng)采購于不同廠商,所以系統(tǒng)間都是相互獨(dú)立的,都有自己的用戶鑒權(quán)認(rèn)證體系,當(dāng)用戶進(jìn)行登錄系統(tǒng)時(shí),不得不記住每套系統(tǒng)的用戶名密碼,同時(shí),管理員也需要為同一個(gè)用戶設(shè)置多套系統(tǒng)登錄賬號(hào),這對系統(tǒng)的使用者來說顯然是不方便的。我們期望的是如果存在多個(gè)系統(tǒng),只需要登錄一次就可以訪問多個(gè)系統(tǒng),只需要在其中一個(gè)系統(tǒng)執(zhí)行注銷登錄操作,則所有的系統(tǒng)都注銷登錄,無需重復(fù)操作,這就是單點(diǎn)登錄(Single Sign On 簡稱SSO)系統(tǒng)實(shí)現(xiàn)的功能。
??單點(diǎn)登錄是系統(tǒng)功能的定義,而實(shí)現(xiàn)單點(diǎn)登錄功能,目前開源且流行的有CAS和OAuth2兩種方式,過去我們用的最多的是CAS,現(xiàn)在隨著SpringCloud的流行,更多人選擇使用SpringSecurity提供的OAuth2認(rèn)證授權(quán)服務(wù)器實(shí)現(xiàn)單點(diǎn)登錄功能。
??OAuth2是一種授權(quán)協(xié)議的標(biāo)準(zhǔn),任何人都可以基于這個(gè)標(biāo)準(zhǔn)開發(fā)Oauth2授權(quán)服務(wù)器,現(xiàn)在百度開放平臺(tái)、騰訊開放平臺(tái)等大部分的開放平臺(tái)都是基于OAuth2協(xié)議實(shí)現(xiàn), OAuth2.0定義了四種授權(quán)類型,最新版OAuth2.1協(xié)議定義了七種授權(quán)類型,其中有兩種因安全問題已不再建議使用:
??通過SpringSecurity官網(wǎng)可知,通過長期的對OAuth2的支持,以及對實(shí)際業(yè)務(wù)的情景考慮,大多數(shù)的系統(tǒng)都不需要授權(quán)服務(wù)器,所以,Spring官方不再推薦使用spring-security-oauth2,SpringSecurity逐漸將spring-security-oauth2中的OAuth2登錄、客戶端、資源服務(wù)器等功能抽取出來,集成在SpringSecurity中,并單獨(dú)新建spring-authorization-server項(xiàng)目實(shí)現(xiàn)授權(quán)服務(wù)器功能。
??目前我們了解最多的是Spring Security OAuth對OAuth2協(xié)議的實(shí)現(xiàn)和支持,這里需要區(qū)分Spring Security OAuth和Spring Security是兩個(gè)項(xiàng)目,過去OAth2相關(guān)功能都在Spring Security OAuth項(xiàng)目中實(shí)現(xiàn),但是自SpringSecurity5.X開始,SpringSecurity項(xiàng)目開始逐漸增加Spring Security OAuth中的功能,自SpringSecurity5.2開始,添加了OAuth 2.0 登錄, 客戶端, 資源服務(wù)器的功能。但授權(quán)服務(wù)器的功能,并不打算集成在SpringSecurity項(xiàng)目中,而是新建了spring-authorization-server項(xiàng)目作為單獨(dú)的授權(quán)服務(wù)器:詳細(xì)介紹。spring-security實(shí)現(xiàn)的是OAuth2.1協(xié)議,spring-security-oauth2實(shí)現(xiàn)的是OAuth2.0協(xié)議。
??Spring未來的計(jì)劃是將 Spring Security OAuth 中當(dāng)前的所有功能構(gòu)建到 Spring Security 5.x 中。 在 Spring Security 達(dá)到與 Spring Security OAuth 的功能對等之后,他們將繼續(xù)支持錯(cuò)誤和安全修復(fù)至少一年。
??因spring-authorization-server目前最新發(fā)布版本0.2.3,部分功能仍在不斷地修復(fù)和完善,還不足以應(yīng)用到實(shí)際生產(chǎn)環(huán)境中,所以,我們目前使用spring-security-oauth2作為授權(quán)服務(wù)器,待后續(xù)spring-authorization-server發(fā)布穩(wěn)定版本后,再進(jìn)行遷移升級(jí)。
??在GitEgg微服務(wù)框架中,gitegg-oauth已經(jīng)引入了spring-security-oauth2,代碼中使用了了Oauth2的密碼授權(quán)和刷新令牌授權(quán),并且自定義擴(kuò)展了【短信驗(yàn)證碼授權(quán)類型】和【圖形驗(yàn)證碼授權(quán)】,這其實(shí)是密碼授權(quán)的擴(kuò)展授權(quán)類型。
??目前,基本上所有的SpringCloud微服務(wù)授權(quán)方式都是使用的OAuth2密碼授權(quán)模式獲取token,可能你會(huì)有疑惑,為什么上面最新的Oauth2協(xié)議已經(jīng)不建議甚至是禁止使用密碼授權(quán)類型了,而我們GitEgg框架的系統(tǒng)管理界面還要使用密碼授權(quán)模式來獲取token?因?yàn)椴唤ㄗh使用密碼授權(quán)類型的原因是第三方客戶端會(huì)收集用戶名密碼,存在安全風(fēng)險(xiǎn)。而在我們這里,我們的客戶端是自有系統(tǒng)管理界面,不是第三方客戶端,所有的用戶名密碼都是我們自有系統(tǒng)的用戶名密碼,只要做好系統(tǒng)安全防護(hù),就可最大限度的避免用戶名密碼泄露給第三方的風(fēng)險(xiǎn)。
spring-security-oauth2單點(diǎn)登錄
??當(dāng)我們的gitegg-oauth作為授權(quán)服務(wù)器使用時(shí),我們希望定制自己的登錄頁等信息,下面我們自定義登錄、主頁、錯(cuò)誤提示頁、找回密碼頁。其他需要的頁面可以自己定義,比如授權(quán)確認(rèn)頁,我們此處業(yè)務(wù)不需要用戶二次確認(rèn),所以這里沒有自定義此頁面。
<!--thymeleaf 模板引擎 渲染單點(diǎn)登錄服務(wù)器頁面-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
/**
* 單點(diǎn)登錄-登錄頁
* @return
*/
@GetMapping("/login") public String login() {
return "login";
}
/**
* 單點(diǎn)登錄-首頁:當(dāng)直接訪問單點(diǎn)登錄系統(tǒng)成功后進(jìn)入的頁面。從客戶端系統(tǒng)進(jìn)入的,直接返回到客戶端頁面
* @return
*/
@GetMapping("/index") public String index() {
return "index";
}
/**
* 單點(diǎn)登錄-錯(cuò)誤頁
* @return
*/
@GetMapping("/error") public String error() {
return "error";
}
/**
* 單點(diǎn)登錄-找回密碼頁
* @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="統(tǒng)一身份認(rèn)證平臺(tái)">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>統(tǒng)一身份認(rèn)證平臺(tái)</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 統(tǒng)一身份認(rèn)證平臺(tái)
</p>
</div>
<div class="login_right">
<div class="title cf">
<ul class="title-list fr cf ">
<li class="on">賬號(hào)密碼登錄</li>
<li>驗(yàn)證碼登錄</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="請輸入您的賬號(hào)">
</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="請輸入手機(jī)號(hào)">
</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="請輸入驗(yàn)證碼">
</div>
<div class="input-code-wrapper">
<a id="sendBtn" href="javascript:sendCode();">獲取驗(yàn)證碼</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: '輸入錯(cuò)誤',
feedbackIcons: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
fields: {
username: {
container: '.input-account-wrapper',
message: '輸入錯(cuò)誤',
validators: {
notEmpty: {
message: '用戶賬號(hào)不能為空'
},
stringLength: {
min: 2,
max: 32,
message: '賬號(hào)長度范圍2-32個(gè)字符。'
},
regexp: {
regexp: /^[a-zA-Z0-9_\.]+$/,
message: '用戶名只能由字母、數(shù)字、點(diǎn)和下劃線組成'
}
}
},
password: {
container: '.input-psw-wrapper',
validators: {
notEmpty: {
message: '密碼不能為空'
},
stringLength: {
min: 5,
max: 32,
message: '密碼長度范圍6-32個(gè)字符。'
}
}
}
}
});
$('.mobile-form').bootstrapValidator({
message: '輸入錯(cuò)誤',
feedbackIcons: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
fields: {
phone: {
message: '輸入錯(cuò)誤',
container: '.input-phone-wrapper',
validators: {
notEmpty: {
message: '手機(jī)號(hào)不能為空'
},
regexp: {
regexp: /^1\d{10}$/,
message: '手機(jī)號(hào)格式錯(cuò)誤'
}
}
},
code: {
container: '.input-sms-wrapper',
validators: {
notEmpty: {
message: '驗(yàn)證碼不能為空'
},
stringLength: {
min: 6,
max: 6,
message: '驗(yàn)證碼長度為6位。'
}
}
}
}
});
var options={
beforeSerialize: beforeFormSerialize,
success: formSuccess,//提交成功后執(zhí)行的回掉函數(shù)
error: formError,//提交失敗后執(zhí)行的回掉函數(shù)
headers : {"TenantId" : 0},
clearForm: true,//提交成功后是否清空表單中的字段值
restForm: true,//提交成功后是否充值表單中的字段值,即恢復(fù)到頁面加載是的狀態(tài)
timeout: 6000//設(shè)置請求時(shí)間,超過時(shí)間后,自動(dòng)退出請求,單位(毫秒)
}
var mobileOptions={
success: mobileFormSuccess,//提交成功后執(zhí)行的回掉函數(shù)
error: mobileFormError,//提交失敗后執(zhí)行的回掉函數(shù)
headers : {"TenantId" : 0},
clearForm: true,//提交成功后是否清空表單中的字段值
restForm: true,//提交成功后是否充值表單中的字段值,即恢復(fù)到頁面加載是的狀態(tài)
timeout: 6000//設(shè)置請求時(shí)間,超過時(shí)間后,自動(dòng)退出請求,單位(毫秒)
}
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",
//數(shù)據(jù),json字符串
data : {
tenantId: tenantId,
phoneNumber: phone,
code: "aliValidateLogin"
},
//請求成功
success : function(result) {
$("#smsId").val(result.data);
},
//請求失敗,包含具體的錯(cuò)誤信息
error : function(e){
console.log(e);
}
});
};
function sendmsg(){
if(countdown==0){
$("#sendBtn").css("color","#181818");
$("#sendBtn").html("獲取驗(yàn)證碼");
countdown=60;
return false;
}
else{
$("#sendBtn").css("color","#74777b");
$("#sendBtn").html("重新發(fā)送("+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認(rèn)證接口,此處網(wǎng)關(guān)放行,由認(rèn)證中心進(jìn)行認(rèn)證
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:手機(jī)驗(yàn)證碼登陸;qr:二維碼掃碼登陸
private static final String SPRING_SECURITY_RESTFUL_TYPE_KEY="type";
// 登陸終端:1:移動(dòng)端登陸,包括微信公眾號(hào)、小程序等;0:PC后臺(tái)登陸
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;
// 手機(jī)驗(yàn)證碼登陸
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);
}
// 賬號(hào)密碼登陸
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授權(quán)服務(wù)器的同時(shí)也提供了單點(diǎn)登錄客戶端的實(shí)現(xiàn),通用通過幾行注解即可實(shí)現(xiàn)單點(diǎn)登錄功能。
1、新建單點(diǎn)登錄客戶端工程,引入oauth2客戶端相關(guān)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、配置單點(diǎn)登錄服務(wù)端相關(guān)信息
server:
port: 8080
servlet:
context-path: /ssoclient1
security:
oauth2:
client:
# 配置在授權(quán)服務(wù)器配置的客戶端id和secret
client-id: ssoclient
client-secret: 123456
# 獲取token的url
access-token-uri: http://127.0.0.1/gitegg-oauth/oauth/token
# 授權(quán)服務(wù)器的授權(quán)地址
user-authorization-uri: http://127.0.0.1/gitegg-oauth/oauth/authorize
resource:
jwt:
# 獲取公鑰的地址,驗(yàn)證token需使用,系統(tǒng)啟動(dòng)時(shí)會(huì)初始化,不會(huì)每次驗(yàn)證都請求
key-uri: http://127.0.0.1/gitegg-oauth/oauth/token_key
1、GitEgg框架中自定義了token返回格式,SpringSecurity獲取token的/oauth/token默認(rèn)返回的是ResponseEntity,自有系統(tǒng)登錄和單點(diǎn)登錄時(shí)需要做轉(zhuǎn)換處理。
2、Gateway網(wǎng)關(guān)鑒權(quán)需要的公鑰地址是gitegg-oauth/oauth/public_key,單點(diǎn)登錄客戶端需要公鑰地址
/oauth/token_key,兩者返回的格式不一樣,需注意區(qū)分。
3、請求/oauth/tonen和/oauth/token_key時(shí),默認(rèn)都需要使用Basic認(rèn)證,也就是請求時(shí)需添加client_id和client_security參數(shù)。
GitEgg: GitEgg 是一款開源免費(fèi)的企業(yè)級(jí)微服務(wù)應(yīng)用開發(fā)框架,旨在整合目前主流穩(wěn)定的開源技術(shù)框架,集成常用的最佳項(xiàng)目解決方案,實(shí)現(xiàn)可直接使用的微服務(wù)快速開發(fā)框架。
GitHub - wmz1930/GitEgg: GitEgg 是一款開源免費(fèi)的企業(yè)級(jí)微服務(wù)應(yīng)用開發(fā)框架,旨在整合目前主流穩(wěn)定的開源技術(shù)框架,集成常用的最佳項(xiàng)目解決方案,實(shí)現(xiàn)可直接使用的微服務(wù)快速開發(fā)框架。
事先安裝好,pycharm
打開File——>Settings——>Projext——>Project Interpriter
點(diǎn)擊加號(hào)(圖中紅圈的地方)
點(diǎn)擊紅圈中的按鈕
選中第一條,點(diǎn)擊鉛筆,將原來的鏈接替換為(這里已經(jīng)替換過了):
https://pypi.tuna.tsinghua.edu.cn/simple/
點(diǎn)擊OK后,輸入requests-html然后回車
選中requests-html后點(diǎn)擊Install Package
等待安裝成功,關(guān)閉
實(shí)例內(nèi)容:
從某博主的所有文章爬取想要的內(nèi)容。
實(shí)例背景:
從(https://me.csdn.net/weixin_44286745)博主的所有文章獲取各文章的標(biāo)題,時(shí)間,閱讀量。
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
網(wǎng)頁分析:
完整代碼:
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大家一起交流學(xué)習(xí),群里也有專業(yè)的大神給你解答難題
文來源于黑馬程序員技術(shù)論壇:http://bbs.itheima.com/thread-430137-1-2.html
Struts2 的Action中若希望訪問Session對象,可采用兩種方式:
1、從ActionContext中獲取;
2、實(shí)現(xiàn)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、實(shí)現(xiàn)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; } }
進(jìn)一步閱讀Struts2.1.8.1源碼,SessionAware接口的實(shí)現(xiàn)方式如下:
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(); }
即在攔截器處理過程中發(fā)現(xiàn)目標(biāo)Action實(shí)現(xiàn)了SessionAware接口,便會(huì)調(diào)用Action中已經(jīng)實(shí)現(xiàn)的setSession(…) 方法,將ActionContext中包裝的Session注入目標(biāo)Action中。目標(biāo)Action也就可以進(jìn)一步對Session進(jìn)行操作了。
*請認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。