色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

為什么使用spring-authorization-server?

Android編程精選 ? 來源:CSDN ? 2023-01-09 15:27 ? 次閱讀

前言

為什么使用spring-authorization-server?

真實原因:原先是因為個人原因,需要研究新版鑒權服務,看到了spring-authorization-server,使用過程中,想著能不能整合新版本cloud,因此此處先以springboot搭建spring-authorization-server,后續再替換為springcloud2021。

官方原因:原先使用Spring Security OAuth,而該項目已經逐漸被淘汰,雖然網上還是有不少該方案,但秉著技術要隨時代更新,從而使用spring-authorization-server

Spring 團隊正式宣布 Spring Security OAuth 停止維護,該項目將不會再進行任何的迭代

6539b948-8f31-11ed-bfe3-dac502259ad0.png

項目構建

以springboot搭建spring-authorization-server(即認證與資源服務器)

數據庫相關表結構構建

需要創建3張表,sql分別如下

CREATETABLE`oauth2_authorization`(
`id`varchar(100)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
`registered_client_id`varchar(100)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
`principal_name`varchar(200)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
`authorization_grant_type`varchar(100)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
`attributes`varchar(4000)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNULLDEFAULTNULL,
`state`varchar(500)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNULLDEFAULTNULL,
`authorization_code_value`blobNULL,
`authorization_code_issued_at`timestamp(0)NULLDEFAULTNULL,
`authorization_code_expires_at`timestamp(0)NULLDEFAULTNULL,
`authorization_code_metadata`varchar(2000)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNULLDEFAULTNULL,
`access_token_value`blobNULL,
`access_token_issued_at`timestamp(0)NULLDEFAULTNULL,
`access_token_expires_at`timestamp(0)NULLDEFAULTNULL,
`access_token_metadata`varchar(2000)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNULLDEFAULTNULL,
`access_token_type`varchar(100)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNULLDEFAULTNULL,
`access_token_scopes`varchar(1000)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNULLDEFAULTNULL,
`oidc_id_token_value`blobNULL,
`oidc_id_token_issued_at`timestamp(0)NULLDEFAULTNULL,
`oidc_id_token_expires_at`timestamp(0)NULLDEFAULTNULL,
`oidc_id_token_metadata`varchar(2000)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNULLDEFAULTNULL,
`refresh_token_value`blobNULL,
`refresh_token_issued_at`timestamp(0)NULLDEFAULTNULL,
`refresh_token_expires_at`timestamp(0)NULLDEFAULTNULL,
`refresh_token_metadata`varchar(2000)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNULLDEFAULTNULL,
PRIMARYKEY(`id`)USINGBTREE
)ENGINE=InnoDBCHARACTERSET=utf8mb4COLLATE=utf8mb4_unicode_ciROW_FORMAT=Dynamic;


CREATETABLE`oauth2_authorization_consent`(
`registered_client_id`varchar(100)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
`principal_name`varchar(200)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
`authorities`varchar(1000)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
PRIMARYKEY(`registered_client_id`,`principal_name`)USINGBTREE
)ENGINE=InnoDBCHARACTERSET=utf8mb4COLLATE=utf8mb4_unicode_ciROW_FORMAT=Dynamic;



CREATETABLE`oauth2_registered_client`(
`id`varchar(100)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
`client_id`varchar(100)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
`client_id_issued_at`timestamp(0)NOTNULLDEFAULTCURRENT_TIMESTAMP(0),
`client_secret`varchar(200)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNULLDEFAULTNULL,
`client_secret_expires_at`timestamp(0)NULLDEFAULTNULL,
`client_name`varchar(200)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
`client_authentication_methods`varchar(1000)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
`authorization_grant_types`varchar(1000)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
`redirect_uris`varchar(1000)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNULLDEFAULTNULL,
`scopes`varchar(1000)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
`client_settings`varchar(2000)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
`token_settings`varchar(2000)CHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ciNOTNULL,
PRIMARYKEY(`id`)USINGBTREE
)ENGINE=InnoDBCHARACTERSET=utf8mb4COLLATE=utf8mb4_unicode_ciROW_FORMAT=Dynamic;
先進行認證服務器相關配置

pom.xml引入依賴

注意!!!spring boot版本需2.6.x以上,是為后面升級成cloud做準備

<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.22version>
dependency>


<dependency>
<groupId>com.xxxx.iovgroupId>
<artifactId>iov-cloud-framework-webartifactId>
<version>2.0.0-SNAPSHOTversion>
<exclusions>

<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
exclusion>
exclusions>
dependency>

<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>2.6.6version>
dependency>


<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.8.0version>
dependency>


<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.39version>
dependency>


<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>


<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-oauth2-authorization-serverartifactId>
<version>0.2.3version>
dependency>


<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-casartifactId>
dependency>


<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>


<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.9version>
dependency>


<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.28version>
dependency>


<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.1version>
dependency>


<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
<version>31.1-jreversion>
dependency>

創建自定義登錄頁面 login.html (可不要,使用自帶的登錄界面)

html>
<htmllang="en"
xmlns:th="https://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<metacharset="utf-8">
<metaname="author"content="test">
<metaname="viewport"content="width=device-width,initial-scale=1">
<metaname="description"content="ThisisaloginpagetemplatebasedonBootstrap5">
<title>LoginPagetitle>
<style>
.is-invalid{
color:red;
}

.invalid-feedback{
color:red;
}

.mb-3{
margin-bottom:3px;
}
style>
<scriptth:inline="javascript">
/**/
if(window!==top){
top.location.href=location.href;
}
script>
head>
<bodyclass="hold-transitionlogin-page">
<divclass="login-box">
<divclass="card">
<divclass="card-bodylogin-card-body">
<pclass="login-box-msg">Signintostartyoursessionp>
<divth:if="${param.error}"class="alertalert-error">
Invalidusernameandpassword.
div>
<divth:if="${param.logout}"class="alertalert-success">
Youhavebeenloggedout.
div>
<formth:action="@{/login}"method="post"id="loginForm">
<divclass="input-groupmb-3">
<inputtype="text"class="form-control"value="zxg"name="username"placeholder="Email"
autocomplete="off">
div>
<divclass="input-groupmb-3">
<inputtype="password"id="password"name="password"value="123"class="form-control"
maxlength="25"placeholder="Password"
autocomplete="off">
div>
<divclass="row">
<divclass="col-4">
<buttontype="submit"id="submitBtn">SignInbutton>
div>
div>
form>
<pclass="mb-1">
<ahref="javascript:void(0)">Iforgotmypassworda>
p>
<pclass="mb-0">
<ahref="javascript:void(0)"class="text-center">Registeranewmembershipa>
p>
div>
div>
div>

<scriptsrc="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js">script>
<scriptsrc="https://cdn.bootcdn.net/ajax/libs/jsencrypt/3.1.0/jsencrypt.min.js">script>
<scriptsrc="https://cdn.bootcdn.net/ajax/libs/jquery-validate/1.9.0/jquery.validate.min.js">script>
<scriptsrc="https://cdn.bootcdn.net/ajax/libs/jquery-validate/1.9.0/additional-methods.min.js">script>

<scriptth:inline="javascript">

$(function(){
varencrypt=newJSEncrypt();

$.validator.setDefaults({
submitHandler:function(form){
console.log("Formsuccessfulsubmitted!");
form.submit();
}
});

});
script>
body>
html>

創建自定義授權頁面 consent.html(可不要,可使用自帶的授權頁面)

html>
<htmllang="en">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width,initial-scale=1,shrink-to-fit=no">
<linkrel="stylesheet"href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z"crossorigin="anonymous">
<title>授權頁面title>
<style>
body{
background-color:aliceblue;
}
style>
<script>
functioncancelConsent(){
document.consent_form.reset();
document.consent_form.submit();
}
script>
head>
<body>
<divclass="container">
<divclass="py-5">
<h1class="text-centertext-primary">用戶授權確認h1>
div>
<divclass="row">
<divclass="coltext-center">
<p>
應用
<ahref="https://felord.cn"><spanclass="font-weight-boldtext-primary"th:text="${clientName}">span>a>
想要訪問您的賬號
<spanclass="font-weight-bold"th:text="${principalName}">span>
p>
div>
div>
<divclass="rowpb-3">
<divclass="coltext-center"><p>上述應用程序請求以下權限<br/>請審閱以下選項并勾選您同意的權限p>div>
div>
<divclass="row">
<divclass="coltext-center">
<formname="consent_form"method="post"action="/oauth2/authorize">
<inputtype="hidden"name="client_id"th:value="${clientId}">
<inputtype="hidden"name="state"th:value="${state}">

<divth:each="scope:${scopes}"class="form-groupform-checkpy-1">
<inputclass="form-check-input"
type="checkbox"
name="scope"
th:value="${scope.scope}"
th:id="${scope.scope}">
<labelclass="form-check-labelfont-weight-bold"th:for="${scope.scope}"th:text="${scope.scope}">label>
<pclass="text-primary"th:text="${scope.description}">p>
div>

<pth:if="${not#lists.isEmpty(previouslyApprovedScopes)}">您已對上述應用授予以下權限:p>
<divth:each="scope:${previouslyApprovedScopes}"class="form-groupform-checkpy-1">
<inputclass="form-check-input"
type="checkbox"
th:id="${scope.scope}"
disabled
checked>
<labelclass="form-check-labelfont-weight-bold"th:for="${scope.scope}"th:text="${scope.scope}">label>
<pclass="text-primary"th:text="${scope.description}">p>
div>

<divclass="form-grouppt-3">
<buttonclass="btnbtn-primarybtn-lg"type="submit"id="submit-consent">
同意授權
button>
div>
<divclass="form-group">
<buttonclass="btnbtn-linkregular"type="button"id="cancel-consent"onclick="cancelConsent();">
取消授權
button>
div>
form>
div>
div>
<divclass="rowpt-4">
<divclass="coltext-center">
<p>
<small>
需要您同意并提供訪問權限。
<br/>如果您不同意,請單擊<spanclass="font-weight-boldtext-primary">取消授權span>,將不會為上述應用程序提供任何您的信息small>
p>
div>
div>
div>
body>
html>

修改配置文件 application.yml(配置內容可自行簡略)

server:
port:9000

spring:
application:
name:authorization-server
thymeleaf:
cache:false
datasource:
url:jdbc//192.168.1.69:3306/test
username:root
password:root
driver-class-name:com.mysql.cj.jdbc.Driver
security:
oauth2:
resourceserver:
jwt:
issuer-uri:http://127.0.0.1:9000#認證中心端點,作為資源端的配置

application:
security:
excludeUrls:#excludeUrls中存放白名單地址
-"/favicon.ico"

#mybatisplus配置
mybatis-plus:
mapper-locations:classpath:/mapper/*Mapper.xml
global-config:
#關閉MP3.0自帶的banner
banner:false
db-config:
#主鍵類型0:"數據庫ID自增",1:"不操作",2:"用戶輸入ID",3:"數字型snowflake",4:"全局唯一IDUUID",5:"字符串型snowflake";
id-type:AUTO
#字段策略
insert-strategy:not_null
update-strategy:not_null
select-strategy:not_null
#駝峰下劃線w轉換
table-underline:true
#邏輯刪除配置
#邏輯刪除全局值(1表示已刪除,這也是MybatisPlus的默認配置)
logic-delete-value:1
#邏輯未刪除全局值(0表示未刪除,這也是MybatisPlus的默認配置)
logic-not-delete-value:0
configuration:
#駝峰
map-underscore-to-camel-case:true
#打開二級緩存
cache-enabled:true
#log-impl:org.apache.ibatis.logging.stdout.StdOutImpl#開啟sql日志

新增認證服務器配置文件 AuthorizationServerConfig

@Configuration(proxyBeanMethods=false)
publicclassAuthorizationServerConfig{
/**
*自定義授權頁面
*使用系統自帶的即不用
*/
privatestaticfinalStringCUSTOM_CONSENT_PAGE_URI="/oauth2/consent";

/**
*自定義UserDetailsService
*/
@Autowired
privateUserServiceuserService;


/**
*
*使用默認配置進行form表單登錄
*OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http)
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
publicSecurityFilterChainauthorizationServerSecurityFilterChain(HttpSecurityhttp)throwsException{
OAuth2AuthorizationServerConfigurerauthorizationServerConfigurer=newOAuth2AuthorizationServerConfigurer<>();

authorizationServerConfigurer.authorizationEndpoint(authorizationEndpoint->authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI));

RequestMatcherendpointsMatcher=authorizationServerConfigurer.getEndpointsMatcher();

http
.requestMatcher(endpointsMatcher)
.userDetailsService(userService)
.authorizeRequests(authorizeRequests->authorizeRequests.anyRequest().authenticated())
.csrf(csrf->csrf.ignoringRequestMatchers(endpointsMatcher))
.apply(authorizationServerConfigurer);
returnhttp.formLogin(Customizer.withDefaults()).build();
}

/**
*注冊客戶端應用
*/
@Bean
publicRegisteredClientRepositoryregisteredClientRepository(JdbcTemplatejdbcTemplate){
//Saveregisteredclientindbasifin-jdbc
RegisteredClientregisteredClient=RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("zxg")
.clientSecret("123")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
//回調地址
.redirectUri("http://www.baidu.com")
//scope自定義的客戶端范圍
.scope(OidcScopes.OPENID)
.scope("message.read")
.scope("message.write")
//client請求訪問時需要授權同意
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
//token配置項信息
.tokenSettings(TokenSettings.builder()
//token有效期100分鐘
.accessTokenTimeToLive(Duration.ofMinutes(100L))
//使用默認JWT相關格式
.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
//開啟刷新token
.reuseRefreshTokens(true)
//refreshToken有效期120分鐘
.refreshTokenTimeToLive(Duration.ofMinutes(120L))
.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256).build()
)
.build();

//Saveregisteredclientindbasifin-memory
JdbcRegisteredClientRepositoryregisteredClientRepository=newJdbcRegisteredClientRepository(jdbcTemplate);
registeredClientRepository.save(registeredClient);
returnregisteredClientRepository;
}

/**
*授權服務:管理OAuth2授權信息服務
*/
@Bean
publicOAuth2AuthorizationServiceauthorizationService(JdbcTemplatejdbcTemplate,RegisteredClientRepositoryregisteredClientRepository){
returnnewJdbcOAuth2AuthorizationService(jdbcTemplate,registeredClientRepository);
}

/**
*授權確認信息處理服務
*/
@Bean
publicOAuth2AuthorizationConsentServiceauthorizationConsentService(JdbcTemplatejdbcTemplate,RegisteredClientRepositoryregisteredClientRepository){
returnnewJdbcOAuth2AuthorizationConsentService(jdbcTemplate,registeredClientRepository);
}

/**
*加載JWK資源
*JWT:指的是JSONWebToken,不存在簽名的JWT是不安全的,存在簽名的JWT是不可竄改的
*JWS:指的是簽過名的JWT,即擁有簽名的JWT
*JWK:既然涉及到簽名,就涉及到簽名算法,對稱加密還是非對稱加密,那么就需要加密的密鑰或者公私鑰對。此處我們將JWT的密鑰或者公私鑰對統一稱為JSONWEBKEY,即JWK。
*/
@Bean
publicJWKSourcejwkSource(){
RSAKeyrsaKey=JwksUtils.generateRsa();
JWKSetjwkSet=newJWKSet(rsaKey);
return(jwkSelector,securityContext)->jwkSelector.select(jwkSet);
}

/**
*配置OAuth2.0提供者元信息
*/
@Bean
publicProviderSettingsproviderSettings(){
returnProviderSettings.builder().issuer("http://127.0.0.1:9000").build();
}

}

新增Security的配置文件WebSecurityConfig

@Configuration
@EnableWebSecurity(debug=true)//開啟Security
publicclassWebSecurityConfig{
@Autowired
privateApplicationPropertiesproperties;

/**
*設置加密方式
*/
@Bean
publicPasswordEncoderpasswordEncoder(){
////將密碼加密方式采用委托方式,默認以BCryptPasswordEncoder方式進行加密,兼容ldap,MD4,MD5等方式
//returnPasswordEncoderFactories.createDelegatingPasswordEncoder();

//此處我們使用明文方式不建議這樣
returnNoOpPasswordEncoder.getInstance();
}

/**
*使用WebSecurity.ignoring()忽略某些URL請求,這些請求將被SpringSecurity忽略
*/
@Bean
WebSecurityCustomizerwebSecurityCustomizer(){
returnnewWebSecurityCustomizer(){
@Override
publicvoidcustomize(WebSecurityweb){
//讀取配置文件application.security.excludeUrls下的鏈接進行忽略
web.ignoring().antMatchers(properties.getSecurity().getExcludeUrls().toArray(newString[]{}));
}
};
}

/**
*針對http請求,進行攔截過濾
*
*CookieCsrfTokenRepository進行CSRF保護的工作方式:
*1.客戶端向服務器發出GET請求,例如請求主頁
*2.Spring發送GET請求的響應以及Set-cookie標頭,其中包含安全生成的XSRF令牌
*/
@Bean
publicSecurityFilterChainhttpSecurityFilterChain(HttpSecurityhttpSecurity)throwsException{
httpSecurity
.authorizeRequests(authorizeRequests->
authorizeRequests.antMatchers("/login").permitAll()
.anyRequest().authenticated()
)

//使用默認登錄頁面
//.formLogin(withDefaults())

//設置form登錄,設置且放開登錄頁login
.formLogin(fromlogin->fromlogin.loginPage("/login").permitAll())

//SpringSecurityCSRF保護
.csrf(csrfToken->csrfToken.csrfTokenRepository(newCookieCsrfTokenRepository()))

////開啟認證服務器的資源服務器相關功能,即需校驗token
//.oauth2ResourceServer()
//.accessDeniedHandler(newSimpleAccessDeniedHandler())
//.authenticationEntryPoint(newSimpleAuthenticationEntryPoint())
//.jwt()
;
returnhttpSecurity.build();
}

}

新增讀取application配置的類 ApplicationProperties

/**
*此步主要是獲取配置文件中配置的白名單,可自行舍去或自定義實現其他方式
**/
@Data
@Component
@ConfigurationProperties("application")
publicclassApplicationProperties{
privatefinalSecuritysecurity=newSecurity();

@Data
publicstaticclassSecurity{
privateOauth2oauth2;
privateListexcludeUrls=newArrayList<>();

@Data
publicstaticclassOauth2{
privateStringissuerUrl;

}
}
}

新增 JwksUtils 類和 KeyGeneratorUtils,這兩個類作為JWT對稱加密

publicfinalclassJwksUtils{

privateJwksUtils(){
}

/**
*生成RSA加密key(即JWK)
*/
publicstaticRSAKeygenerateRsa(){
//生成RSA加密的key
KeyPairkeyPair=KeyGeneratorUtils.generateRsaKey();
//公鑰
RSAPublicKeypublicKey=(RSAPublicKey)keyPair.getPublic();
//私鑰
RSAPrivateKeyprivateKey=(RSAPrivateKey)keyPair.getPrivate();
//構建RSA加密key
returnnewRSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}

/**
*生成EC加密key(即JWK)
*/
publicstaticECKeygenerateEc(){
//生成EC加密的key
KeyPairkeyPair=KeyGeneratorUtils.generateEcKey();
//公鑰
ECPublicKeypublicKey=(ECPublicKey)keyPair.getPublic();
//私鑰
ECPrivateKeyprivateKey=(ECPrivateKey)keyPair.getPrivate();
//根據公鑰參數生成曲線
Curvecurve=Curve.forECParameterSpec(publicKey.getParams());
//構建EC加密key
returnnewECKey.Builder(curve,publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}

/**
*生成HmacSha256密鑰
*/
publicstaticOctetSequenceKeygenerateSecret(){
SecretKeysecretKey=KeyGeneratorUtils.generateSecretKey();
returnnewOctetSequenceKey.Builder(secretKey)
.keyID(UUID.randomUUID().toString())
.build();
}
}


classKeyGeneratorUtils{

privateKeyGeneratorUtils(){
}

/**
*生成RSA密鑰
*/
staticKeyPairgenerateRsaKey(){
KeyPairkeyPair;
try{
KeyPairGeneratorkeyPairGenerator=KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair=keyPairGenerator.generateKeyPair();
}catch(Exceptionex){
thrownewIllegalStateException(ex);
}
returnkeyPair;
}

/**
*生成EC密鑰
*/
staticKeyPairgenerateEcKey(){
EllipticCurveellipticCurve=newEllipticCurve(
newECFieldFp(
newBigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853951")),
newBigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"),
newBigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291"));
ECPointecPoint=newECPoint(
newBigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"),
newBigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109"));
ECParameterSpececParameterSpec=newECParameterSpec(
ellipticCurve,
ecPoint,
newBigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"),
1);

KeyPairkeyPair;
try{
KeyPairGeneratorkeyPairGenerator=KeyPairGenerator.getInstance("EC");
keyPairGenerator.initialize(ecParameterSpec);
keyPair=keyPairGenerator.generateKeyPair();
}catch(Exceptionex){
thrownewIllegalStateException(ex);
}
returnkeyPair;
}

/**
*生成HmacSha256密鑰
*/
staticSecretKeygenerateSecretKey(){
SecretKeyhmacKey;
try{
hmacKey=KeyGenerator.getInstance("HmacSha256").generateKey();
}catch(Exceptionex){
thrownewIllegalStateException(ex);
}
returnhmacKey;
}
}

新建 ConsentController,編寫登錄和認證頁面的跳轉

如果在上面沒有使用自定義的登錄和授權頁面,下面的跳轉方法按需舍去

@Slf4j
@Controller
publicclassConsentController{

privatefinalRegisteredClientRepositoryregisteredClientRepository;
privatefinalOAuth2AuthorizationConsentServiceauthorizationConsentService;

publicConsentController(RegisteredClientRepositoryregisteredClientRepository,
OAuth2AuthorizationConsentServiceauthorizationConsentService){
this.registeredClientRepository=registeredClientRepository;
this.authorizationConsentService=authorizationConsentService;
}

@ResponseBody
@GetMapping("/favicon.ico")
publicStringfaviconico(){
return"favicon.ico";
}

@GetMapping("/login")
publicStringloginPage(){
return"login";
}

@GetMapping(value="/oauth2/consent")
publicStringconsent(Principalprincipal,Modelmodel,
@RequestParam(OAuth2ParameterNames.CLIENT_ID)StringclientId,
@RequestParam(OAuth2ParameterNames.SCOPE)Stringscope,
@RequestParam(OAuth2ParameterNames.STATE)Stringstate){

//Removescopesthatwerealreadyapproved
SetscopesToApprove=newHashSet<>();
SetpreviouslyApprovedScopes=newHashSet<>();
RegisteredClientregisteredClient=this.registeredClientRepository.findByClientId(clientId);
OAuth2AuthorizationConsentcurrentAuthorizationConsent=
this.authorizationConsentService.findById(registeredClient.getId(),principal.getName());
SetauthorizedScopes;
if(currentAuthorizationConsent!=null){
authorizedScopes=currentAuthorizationConsent.getScopes();
}else{
authorizedScopes=Collections.emptySet();
}
for(StringrequestedScope:StringUtils.delimitedListToStringArray(scope,"")){
if(authorizedScopes.contains(requestedScope)){
previouslyApprovedScopes.add(requestedScope);
}else{
scopesToApprove.add(requestedScope);
}
}

model.addAttribute("clientId",clientId);
model.addAttribute("state",state);
model.addAttribute("scopes",withDescription(scopesToApprove));
model.addAttribute("previouslyApprovedScopes",withDescription(previouslyApprovedScopes));
model.addAttribute("principalName",principal.getName());

return"consent";
}

privatestaticSetwithDescription(Setscopes){
SetscopeWithDescriptions=newHashSet<>();
for(Stringscope:scopes){
scopeWithDescriptions.add(newScopeWithDescription(scope));

}
returnscopeWithDescriptions;
}

publicstaticclassScopeWithDescription{
privatestaticfinalStringDEFAULT_DESCRIPTION="UNKNOWNSCOPE-Wecannotprovideinformationaboutthispermission,usecautionwhengrantingthis.";
privatestaticfinalMapscopeDescriptions=newHashMap<>();
static{
scopeDescriptions.put(
"message.read",
"Thisapplicationwillbeabletoreadyourmessage."
);
scopeDescriptions.put(
"message.write",
"Thisapplicationwillbeabletoaddnewmessages.Itwillalsobeabletoeditanddeleteexistingmessages."
);
scopeDescriptions.put(
"other.scope",
"Thisisanotherscopeexampleofascopedescription."
);
}

publicfinalStringscope;
publicfinalStringdescription;

ScopeWithDescription(Stringscope){
this.scope=scope;
this.description=scopeDescriptions.getOrDefault(scope,DEFAULT_DESCRIPTION);
}
}

}

新建 UserController,User,UserService等標準的自定義用戶業務,此處僅放出UserServiceImpl

@RequiredArgsConstructor
@Slf4j
@Component
classUserServiceImplimplementsUserService{
privatefinalUserMapperuserMapper;

@Override
publicUserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException{
Useruser=userMapper.selectOne(newLambdaQueryWrapper().eq(User::getUsername,username));
returnneworg.springframework.security.core.userdetails.User(username,user.getPassword(),newArrayList<>());
}
}

啟動項目,如下圖

65494228-8f31-11ed-bfe3-dac502259ad0.png

認證服務器整體結構圖

6574f22e-8f31-11ed-bfe3-dac502259ad0.png
資源服務器相關配置

pom.xml引入資源服務器相關依賴


<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-oauth2-resource-serverartifactId>
dependency>


<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>

新增配置文件 application.yaml

server:
port:9003
spring:
application:
name:resource
security:
oauth2:
resourceserver:
jwt:
issuer-uri:http://127.0.0.1:9000
feign:
client:
config:
default:#配置超時時間
connect-timeout:10000
read-timeout:10000

新增資源服務器配置文件 ResourceServerConfiguration

@Configuration
@EnableWebSecurity(debug=true)
@EnableGlobalMethodSecurity(prePostEnabled=true)//開啟鑒權服務
publicclassResourceServerConfiguration{

@Bean
publicSecurityFilterChainhttpSecurityFilterChain(HttpSecurityhttpSecurity)throwsException{
//所有請求都進行攔截
httpSecurity.authorizeRequests().anyRequest().authenticated();
//關閉session
httpSecurity.sessionManagement().disable();
//配置資源服務器的無權限,無認證攔截器等以及JWT驗證
httpSecurity.oauth2ResourceServer()
.accessDeniedHandler(newSimpleAccessDeniedHandler())
.authenticationEntryPoint(newSimpleAuthenticationEntryPoint())
.jwt();
returnhttpSecurity.build();
}

}

新增相關無認證無權限統一攔截回復 SimpleAccessDeniedHandlerSimpleAuthenticationEntryPoint

/**
*攜帶了token而且token合法但是權限不足以訪問其請求的資源403
*@authorzxg
*/
publicclassSimpleAccessDeniedHandlerimplementsAccessDeniedHandler{

@Override
publicvoidhandle(HttpServletRequestrequest,HttpServletResponseresponse,AccessDeniedExceptionaccessDeniedException)throwsIOException,ServletException{
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setCharacterEncoding("utf-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
ObjectMapperobjectMapper=newObjectMapper();
StringresBody=objectMapper.writeValueAsString(SingleResultBundle.failed("無權訪問"));
PrintWriterprintWriter=response.getWriter();
printWriter.print(resBody);
printWriter.flush();
printWriter.close();
}
}


/**
*在資源服務器中不攜帶token或者token無效401
*@authorzxg
*/
@Slf4j
publicclassSimpleAuthenticationEntryPointimplementsAuthenticationEntryPoint{
@Override
publicvoidcommence(HttpServletRequestrequest,HttpServletResponseresponse,AuthenticationExceptionauthException)throwsIOException,ServletException{
if(response.isCommitted()){
return;
}

Throwablethrowable=authException.fillInStackTrace();

StringerrorMessage="認證失敗";

if(throwableinstanceofBadCredentialsException){
errorMessage="錯誤的客戶端信息";
}else{
Throwablecause=authException.getCause();

if(causeinstanceofJwtValidationException){
log.warn("JWTToken過期,具體內容:"+cause.getMessage());
errorMessage="無效的token信息";
}elseif(causeinstanceofBadJwtException){
log.warn("JWT簽名異常,具體內容:"+cause.getMessage());
errorMessage="無效的token信息";
}elseif(causeinstanceofAccountExpiredException){
errorMessage="賬戶已過期";
}elseif(causeinstanceofLockedException){
errorMessage="賬戶已被鎖定";
//}elseif(causeinstanceofInvalidClientException||causeinstanceofBadClientCredentialsException){
//response.getWriter().write(JSON.toJSONString(SingleResultBundle.failed(401,"無效的客戶端")));
//}elseif(causeinstanceofInvalidGrantException||causeinstanceofRedirectMismatchException){
//response.getWriter().write(JSON.toJSONString(SingleResultBundle.failed("無效的類型")));
//}elseif(causeinstanceofUnauthorizedClientException){
//response.getWriter().write(JSON.toJSONString(SingleResultBundle.failed("未經授權的客戶端")));
}elseif(throwableinstanceofInsufficientAuthenticationException){
Stringmessage=throwable.getMessage();
if(message.contains("Invalidtokendoesnotcontainresourceid")){
errorMessage="未經授權的資源服務器";
}elseif(message.contains("Fullauthenticationisrequiredtoaccessthisresource")){
errorMessage="缺少驗證信息";
}
}else{
errorMessage="驗證異常";
}
}

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setCharacterEncoding("utf-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
ObjectMapperobjectMapper=newObjectMapper();
StringresBody=objectMapper.writeValueAsString(SingleResultBundle.failed(errorMessage));
PrintWriterprintWriter=response.getWriter();
printWriter.print(resBody);
printWriter.flush();
printWriter.close();
}
}

新增 ResourceController 進行接口測試

@Slf4j
@RestController
publicclassResourceController{

/**
*測試SpringAuthorizationServer,測試權限
*/
@PreAuthorize("hasAuthority('SCOPE_message.read')")
@GetMapping("/getTest")
publicStringgetTest(){
return"getTest";
}

/**
*默認登錄成功跳轉頁為/防止404狀態
*
*@returnthemap
*/
@GetMapping("/")
publicMapindex(){
returnCollections.singletonMap("msg","loginsuccess!");
}

@GetMapping("/getResourceTest")
publicSingleResultBundlegetResourceTest(){
returnSingleResultBundle.success("這是resource的測試方法getResourceTest()");
}
}

啟動項目,效果如下

658e2a32-8f31-11ed-bfe3-dac502259ad0.png

項目總體結構如下

65a090fa-8f31-11ed-bfe3-dac502259ad0.png
測試認證鑒權
#調用/oauth2/authorize,獲取code
http://127.0.0.1:9000/oauth2/authorize?client_id=zxg&response_type=code&scope=message.read&redirect_uri=http://www.baidu.com
#會判斷是否登錄,若沒有,則跳轉到登錄頁面,如下圖1
#登錄完成后,會提示是否授權,若沒有,則跳轉到授權界面,如下圖2
#授權成功后,跳轉到回調地址,并帶上code,如圖3
65db3ebc-8f31-11ed-bfe3-dac502259ad0.png65f825d6-8f31-11ed-bfe3-dac502259ad0.png

打開postman,進行獲取access_token

#訪問/oauth2/token地址
#在Authorization中選擇BasicAuth模式,填入對應客戶端,其會在header中生成Authorization,如下圖右側
6618d678-8f31-11ed-bfe3-dac502259ad0.png

返回結果如下

6644dade-8f31-11ed-bfe3-dac502259ad0.png

調用ResourceController中的接口,測試token是否生效

667bd0ca-8f31-11ed-bfe3-dac502259ad0.png

源碼下載地址

  • https://gitee.com/rjj521/authorization-server-learn

總結

至此,spring-authorization-server的基礎使用已完成,總體上和原Spring Security OAuth大差不差,個別配置項不同。期間在網上搜尋了很多資料,然后進行整合,因此文中存在與其他網上教程相同代碼,如有爭議,請聯系我刪除改正,謝謝。


	

審核編輯 :李倩


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 服務器
    +關注

    關注

    12

    文章

    9231

    瀏覽量

    85625
  • 數據庫
    +關注

    關注

    7

    文章

    3822

    瀏覽量

    64506
  • spring
    +關注

    關注

    0

    文章

    340

    瀏覽量

    14353

原文標題:擁抱 Spring 全新 OAuth 解決方案:spring-authorization-server 該怎么玩?

文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    AI Server市場持續擴張,2025年產值有望逼近3000億美元

     在2024年,全球Server市場的總產值預計將達到約3060億美元。其中,AI Server的增長動力顯著超越了一般型Server,其產值約為2050億美元。隨著2025年AI Serve
    的頭像 發表于 01-07 17:18 ?331次閱讀

    Spring 應用合并之路(二):峰回路轉,柳暗花明

    作者:京東科技 李君 書接上文,前面在 Spring 應用合并之路(一):摸石頭過河 介紹了幾種不成功的經驗,下面繼續折騰… 四、倉庫合并,獨立容器 在經歷了上面的嘗試,在同事為啥不搞兩個獨立的容器
    的頭像 發表于 12-12 11:22 ?761次閱讀

    虛擬化數據恢復—VMware ESX SERVER無法連接STORAGE的數據恢復案例

    某單位信息管理平臺,數臺VMware ESX SERVER共享一臺某品牌DS4100存儲。 vc報告虛擬磁盤丟失,管理員ssh到ESX中執行fdisk -l查看磁盤,發現STORAGE中的分區表不見了。重啟所有設備后,ESX SERVER均無法連接到DS4100存儲中的
    的頭像 發表于 11-15 11:17 ?158次閱讀

    Spring事務實現原理

    作者:京東零售 范錫軍 1、引言 springspring-tx模塊提供了對事務管理支持,使用spring事務可以讓我們從復雜的事務處理中得到解脫,無需要去處理獲得連接、關閉連接、事務提交和回滾等
    的頭像 發表于 11-08 10:10 ?841次閱讀
    <b class='flag-5'>Spring</b>事務實現原理

    Nat server技術原理和配置過程

    Nat server:指定公有地址:端口和私有地址:端口形成一對一映射關系——映射表。這也是Nat server與其他nat的區別之一,Nat server可以指定端口進行映射。
    的頭像 發表于 10-10 14:38 ?788次閱讀
    Nat <b class='flag-5'>server</b>技術原理和配置過程

    數據庫數據恢復—SQL Server數據庫出現823錯誤的數據恢復案例

    SQL Server數據庫故障: SQL Server附加數據庫出現錯誤823,附加數據庫失敗。數據庫沒有備份,無法通過備份恢復數據庫。 SQL Server數據庫出現823錯誤的可能原因有:數據庫物理頁面損壞、數據庫物理頁
    的頭像 發表于 09-20 11:46 ?373次閱讀
    數據庫數據恢復—SQL <b class='flag-5'>Server</b>數據庫出現823錯誤的數據恢復案例

    Spring Cloud Gateway網關框架

    Spring Cloud Gateway網關框架 本軟件微服務架構中采用Spring Cloud Gateway網關控制框架,Spring Cloud Gateway是Spring C
    的頭像 發表于 08-22 09:58 ?510次閱讀
    <b class='flag-5'>Spring</b> Cloud Gateway網關框架

    如何使用AT固件開啟ESP32 Ethernet DHCP Server模式?

    使用 AT 固件進行以太網應用,默認 Ethernet 開啟的是 DHCP Client 模式, 如何使用 AT 指令設置 ESP32 Etherner 為 DHCP Server 模式呢
    發表于 06-27 06:02

    玩轉Spring狀態機

    說起Spring狀態機,大家很容易聯想到這個狀態機和設計模式中狀態模式的區別是啥呢?沒錯,Spring狀態機就是狀態模式的一種實現,在介紹Spring狀態機之前,讓我們來看看設計模式中的狀態模式
    的頭像 發表于 06-25 14:21 ?973次閱讀
    玩轉<b class='flag-5'>Spring</b>狀態機

    examples\\protocols\\https_server\\simple不工作怎么處理?

    請幫忙, 瀏覽器顯示: Header fields are too long for server to interpret ets Jun8 2016 00:22:57 rst:0x1
    發表于 06-20 07:04

    如何移植http/https server到softAP上?

    有沒有什么 思路,現在要把 worksapceesp-idfcomponentsesp_http_server worksapceesp-idfcomponentsesp_https_server
    發表于 06-19 06:14

    微軟發布Windows Server 2025 LTSC Build 26212.5000預覽版,新增功能詳析

    據悉,此版Windows Server包括數據中心版在內的所有版本都提供了桌面體驗(Desktop Experience)和核心服務(Server Core)兩種安裝選擇。
    的頭像 發表于 05-09 16:02 ?1736次閱讀

    微軟向Windows Server 2022推送Copilot應用

    微軟通過Microsoft Edge瀏覽器的更新方式,向Windows 11與Windows 10系統用戶內置推薦Copilot應用。本次更新包大小僅有16KB。盡管如此,這一舉動在Windows Server 2022用戶中尚無明確反饋。
    的頭像 發表于 04-17 16:33 ?539次閱讀

    怎么把stlink server添加到keil里?

    想會用STM32cube monitor和keil 同時使用,但是仿真器里面沒有stlink server這個工具。即使安裝了stlink server也沒有用。stm32cube IDE則有。看
    發表于 03-27 07:28

    Spring事務傳播性的相關知識

    本文主要介紹了Spring事務傳播性的相關知識。
    的頭像 發表于 01-10 09:29 ?464次閱讀
    <b class='flag-5'>Spring</b>事務傳播性的相關知識
    主站蜘蛛池模板: 娇妻中日久久持久久| 国产日韩久久久精品影院首页 | 国产精品嫩草影视在线观看| 99视频偷窥在线精品国自产拍| 一本之道加勒比在线观看| 吸奶舔下面| 性生大片免费看| 亚洲国产欧美国产综合在线| 学校女性奴sm训练调教| 小夫妻天天恶战| 亚洲国产高清在线| 亚洲国产AV精品一区二区蜜芽| 小莹的性荡生活45章| 亚洲99精品A片久久久久久| 学校女性奴sm训练调教| 亚洲精品不卡在线| 亚洲性无码av在线| 在线观看插女生免费版| 2021自产拍在线观看视频| 6080YYY午夜理论片在线观看| 97视频在线观看视频最新| GAY空少被体育生开菊| 成人天堂资源WWW在线| 国产精品AV无码免费播放| 国语对白老女人8av| 久草在线一免费新视频| 美女xx00| 日本精品久久久久中文字幕 1| 试看做受120秒免费午夜剧场| 贤妻良母电影日本| 亚洲性夜夜色综合网站| 中文字幕视频免费在线观看| 99热都是精品| 囯产精品久久久久免费蜜桃 | 国产一区二区在线免费观看| 护士被老头边摸边吃奶的视频| 久久re视频这里精品青| 男人和女人一起愁愁愁很痛| 日本色呦呦| 亚洲精品久久99蜜芽尤物TV| 最新影音先锋av资源台|