Springboot開(kāi)發(fā)OAuth2認(rèn)證授權(quán)與資源服務(wù)器操作
設(shè)計(jì)并開(kāi)發(fā)一個(gè)開(kāi)放平臺(tái)。
一、設(shè)計(jì):

網(wǎng)關(guān)可以 與認(rèn)證授權(quán)服務(wù)合在一起,也可以分開(kāi)。
二、開(kāi)發(fā)與實(shí)現(xiàn):
用Oauth2技術(shù)對(duì)訪問(wèn)受保護(hù)的資源的客戶端進(jìn)行認(rèn)證與授權(quán)。
Oauth2技術(shù)應(yīng)用的關(guān)鍵是:
1)服務(wù)器對(duì)OAuth2客戶端進(jìn)行認(rèn)證與授權(quán)。
2)Token的發(fā)放。
3)通過(guò)access_token訪問(wèn)受OAuth2保護(hù)的資源。
選用的關(guān)鍵技術(shù):Springboot, Spring-security, Spring-security-oauth2。
提供一個(gè)簡(jiǎn)化版,用戶、token數(shù)據(jù)保存在內(nèi)存中,用戶與客戶端的認(rèn)證授權(quán)服務(wù)、資源服務(wù),都是在同一個(gè)工程中。現(xiàn)實(shí)項(xiàng)目中,技術(shù)架構(gòu)通常上將用戶與客戶端的認(rèn)證授權(quán)服務(wù)設(shè)計(jì)在一個(gè)子系統(tǒng)(工程)中,而資源服務(wù)設(shè)計(jì)為另一個(gè)子系統(tǒng)(工程)。
1、Spring-security對(duì)用戶身份進(jìn)行認(rèn)證授權(quán):
主要作用是對(duì)用戶身份通過(guò)用戶名與密碼的方式進(jìn)行認(rèn)證并且授權(quán)。
package com.banling.oauth2server.config;
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.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
//用戶信息保存在內(nèi)存中
//在鑒定角色roler時(shí),會(huì)默認(rèn)加上ROLLER_前綴
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER").and()
.withUser("test").password("test").roles("TEST");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() //登記界面,默認(rèn)是permit All
.and()
.authorizeRequests().antMatchers("/","/home").permitAll() //不用身份認(rèn)證可以訪問(wèn)
.and()
.authorizeRequests().anyRequest().authenticated() //其它的請(qǐng)求要求必須有身份認(rèn)證
.and()
.csrf() //防止CSRF(跨站請(qǐng)求偽造)配置
.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize")).disable();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
配置用戶信息,保存在內(nèi)存中。也可以自定義將用戶數(shù)據(jù)保存在數(shù)據(jù)庫(kù)中,實(shí)現(xiàn)UserDetailsService接口,進(jìn)行認(rèn)證與授權(quán),略。
配置訪問(wèn)哪些URL需要授權(quán)。必須配置authorizeRequests(),否則啟動(dòng)報(bào)錯(cuò),說(shuō)是沒(méi)有啟用security技術(shù)。
注意,在這里的身份進(jìn)行認(rèn)證與授權(quán)沒(méi)有涉及到OAuth的技術(shù):
當(dāng)訪問(wèn)要授權(quán)的URL時(shí),請(qǐng)求會(huì)被DelegatingFilterProxy攔截,如果還沒(méi)有授權(quán),請(qǐng)求就會(huì)被重定向到登錄界面。在登錄成功(身份認(rèn)證并授權(quán))后,請(qǐng)求被重定向至之前訪問(wèn)的URL。
2、OAuth2的授權(quán)服務(wù):
主要作用是OAuth2的客戶端進(jìn)行認(rèn)證與授權(quán)。
package com.banling.oauth2server.config;
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.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.TokenApprovalStore;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter{
@Autowired
private TokenStore tokenStore;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private ApprovalStore approvalStore;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//添加客戶端信息
//使用內(nèi)存存儲(chǔ)OAuth客戶端信息
clients.inMemory()
// client_id
.withClient("client")
// client_secret
.secret("secret")
// 該client允許的授權(quán)類型,不同的類型,則獲得token的方式不一樣。
.authorizedGrantTypes("authorization_code","implicit","refresh_token")
.resourceIds("resourceId")
//回調(diào)uri,在authorization_code與implicit授權(quán)方式時(shí),用以接收服務(wù)器的返回信息
.redirectUris("http://localhost:8090/")
// 允許的授權(quán)范圍
.scopes("app","test");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//reuseRefreshTokens設(shè)置為false時(shí),每次通過(guò)refresh_token獲得access_token時(shí),也會(huì)刷新refresh_token;也就是說(shuō),會(huì)返回全新的access_token與refresh_token。
//默認(rèn)值是true,只返回新的access_token,refresh_token不變。
endpoints.tokenStore(tokenStore).approvalStore(approvalStore).reuseRefreshTokens(false)
.authenticationManager(authenticationManager);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.realm("OAuth2-Sample")
.allowFormAuthenticationForClients()
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
@Bean
public TokenStore tokenStore() {
//token保存在內(nèi)存中(也可以保存在數(shù)據(jù)庫(kù)、Redis中)。
//如果保存在中間件(數(shù)據(jù)庫(kù)、Redis),那么資源服務(wù)器與認(rèn)證服務(wù)器可以不在同一個(gè)工程中。
//注意:如果不保存access_token,則沒(méi)法通過(guò)access_token取得用戶信息
return new InMemoryTokenStore();
}
@Bean
public ApprovalStore approvalStore() throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
}
配置OAuth2的客戶端信息:clientId、client_secret、authorization_type、redirect_url等。本例是將數(shù)據(jù)保存在內(nèi)存中。也可以保存在數(shù)據(jù)庫(kù)中,實(shí)現(xiàn)ClientDetailsService接口,進(jìn)行認(rèn)證與授權(quán),略。
TokenStore是access_token的存儲(chǔ)單元,可以保存在內(nèi)存、數(shù)據(jù)庫(kù)、Redis中。本例是保存在內(nèi)存中。
3、OAuth2的資源服務(wù):
主要作用是配置資源受保護(hù)的OAuth2策略。
package com.banling.oauth2server.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
@Configuration
@EnableResourceServer
public class ResServerConfig extends ResourceServerConfigurerAdapter{
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.tokenStore(tokenStore)
.resourceId("resourceId");
}
@Override
public void configure(HttpSecurity http) throws Exception {
/*
注意:
1、必須先加上: .requestMatchers().antMatchers(...),表示對(duì)資源進(jìn)行保護(hù),也就是說(shuō),在訪問(wèn)前要進(jìn)行OAuth認(rèn)證。
2、接著:訪問(wèn)受保護(hù)的資源時(shí),要具有哪里權(quán)限。
------------------------------------
否則,請(qǐng)求只是被Security的攔截器攔截,請(qǐng)求根本到不了OAuth2的攔截器。
同時(shí),還要注意先配置:security.oauth2.resource.filter-order=3,否則通過(guò)access_token取不到用戶信息。
------------------------------------
requestMatchers()部分說(shuō)明:
Invoking requestMatchers() will not override previous invocations of ::
mvcMatcher(String)}, requestMatchers(), antMatcher(String), regexMatcher(String), and requestMatcher(RequestMatcher).
*/
http
// Since we want the protected resources to be accessible in the UI as well we need
// session creation to be allowed (it's disabled by default in 2.0.6)
//另外,如果不設(shè)置,那么在通過(guò)瀏覽器訪問(wèn)被保護(hù)的任何資源時(shí),每次是不同的SessionID,并且將每次請(qǐng)求的歷史都記錄在OAuth2Authentication的details的中
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.requestMatchers()
.antMatchers("/user","/res/**")
.and()
.authorizeRequests()
.antMatchers("/user","/res/**")
.authenticated();
}
}
配置哪些URL資源是受OAuth2保護(hù)的。注意,必須配置sessionManagement(),否則訪問(wèn)受護(hù)資源請(qǐng)求不會(huì)被OAuth2的攔截器ClientCredentialsTokenEndpointFilter與OAuth2AuthenticationProcessingFilter攔截,也就是說(shuō),沒(méi)有配置的話,資源沒(méi)有受到OAuth2的保護(hù)。
4、受OAuth2保存的資源:
1)獲取OAuth2客戶端的信息
package com.banling.oauth2server.web;
import java.security.Principal;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@RequestMapping("/user")
public Principal user(Principal principal) {
//principal在經(jīng)過(guò)security攔截后,是org.springframework.security.authentication.UsernamePasswordAuthenticationToken
//在經(jīng)OAuth2攔截后,是OAuth2Authentication
return principal;
}
}
2)其它受保護(hù)的資源
package com.banling.oauth2server.web;
import java.security.Principal;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 作為OAuth2的資源服務(wù)時(shí),不能在Controller(或者RestController)注解上寫上URL,因?yàn)檫@樣不會(huì)被識(shí)別,會(huì)報(bào)404錯(cuò)誤。<br>
*<br> {
*<br> "timestamp": 1544580859138,
*<br> "status": 404,
*<br> "error": "Not Found",
*<br> "message": "No message available",
*<br> "path": "/res/getMsg"
*<br> }
*<br>
*
*
*/
@RestController()//作為資源服務(wù)時(shí),不能帶上url,@RestController("/res")是錯(cuò)的,無(wú)法識(shí)別。只能在方法上注解全路徑
public class ResController {
@RequestMapping("/res/getMsg")
public String getMsg(String msg,Principal principal) {//principal中封裝了客戶端(用戶,也就是clientDetails,區(qū)別于Security的UserDetails,其實(shí)clientDetails中也封裝了UserDetails),不是必須的參數(shù),除非你想得到用戶信息,才加上principal。
return "Get the msg: "+msg;
}
}
5、application.properties配置:
security.oauth2.resource.filter-order=3 必須配置,否則對(duì)受護(hù)資源請(qǐng)求不會(huì)被OAuth2的攔截器攔截。
6、測(cè)試
1)authorization_code方式獲取code,然后再通過(guò)code獲取access_token(和refresh_token)。
在瀏覽輸入:
http://localhost:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://localhost:8090/
在登錄界面輸入用戶名與密碼user/user,提交。
提交后服務(wù)重定向 至scope的授權(quán)界面:

授權(quán)后,在回調(diào)uri中可以得從code:

用postman工具,設(shè)置header的值,通過(guò)code獲取access_token與fresh_token:

2)implict方式接獲取access_token。
瀏覽器中輸入:
http://localhost:8080/oauth/authorize?client_id=client&response_type=token&redirect_uri=http://localhost:8090/
可以直接獲得access_token。

3)通過(guò)refresh_token獲取access_token與refresh_token。
用postman工具測(cè)試,根據(jù)refresh_token獲取新的access_token與fresh_token。

4)獲取OAuth2客戶端的信息。
可以通過(guò)get方式,也可以通過(guò)設(shè)置header獲取。
get方式,看url字符串:

設(shè)置header的方式:

5)訪問(wèn)其它受保護(hù)的資源

github上的源碼: https://github.com/banat020/OAuth2-server
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot日期格式化及時(shí)差問(wèn)題分析
這篇文章主要介紹了springboot日期格式化,時(shí)差問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12
在Java中如何避免創(chuàng)建不必要的對(duì)象
作為Java開(kāi)發(fā)者,我們每天創(chuàng)建很多對(duì)象,但如何才能避免創(chuàng)建不必要的對(duì)象呢?這需要我們好好學(xué)習(xí),這篇文章主要給大家介紹了關(guān)于在Java中如何避免創(chuàng)建不必要對(duì)象的相關(guān)資料,需要的朋友可以參考下2021-10-10
Java中String類詳解之字符串操作的必備知識(shí)
Java String類是Java編程語(yǔ)言中的核心類,用于高效處理字符串操作,它在面試中常被問(wèn)到,因?yàn)槔斫馄涮匦耘c用法對(duì)于編寫高質(zhì)量的代碼至關(guān)重要,這篇文章主要介紹了Java中String類詳解之字符串操作的必備知識(shí),需要的朋友可以參考下2025-09-09
Springboot項(xiàng)目實(shí)現(xiàn)Mysql多數(shù)據(jù)源切換的完整實(shí)例
這篇文章主要給大家介紹了關(guān)于Springboot項(xiàng)目實(shí)現(xiàn)Mysql多數(shù)據(jù)源切換的完整實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Netty實(shí)現(xiàn)自定義協(xié)議編解碼器
這篇文章主要為大家介紹了Netty實(shí)現(xiàn)自定義協(xié)議編解碼器示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
java實(shí)現(xiàn)響應(yīng)重定向發(fā)送post請(qǐng)求操作示例
這篇文章主要介紹了java實(shí)現(xiàn)響應(yīng)重定向發(fā)送post請(qǐng)求操作,結(jié)合實(shí)例形式分析了java請(qǐng)求響應(yīng)、重定向及數(shù)據(jù)處理相關(guān)操作技巧,需要的朋友可以參考下2020-04-04
SpringBoot項(xiàng)目屬性配置(application.properties、yml和yaml)
SpringBoot支持多種配置文件格式,包括application.properties、yml和yaml,application.properties是鍵值對(duì)結(jié)構(gòu),常用于簡(jiǎn)單配置,如設(shè)置服務(wù)器端口,yml和yaml文件格式相同,使用縮進(jìn)表示層級(jí),適合復(fù)雜配置,下面就來(lái)詳細(xì)的介紹一下2025-08-08
Java實(shí)現(xiàn)將HTML文件與字符串轉(zhuǎn)為Word
在實(shí)際開(kāi)發(fā)中,將HTML頁(yè)面或內(nèi)容轉(zhuǎn)換為Word文檔是一項(xiàng)常見(jiàn)需求,本文將為大家簡(jiǎn)單介紹一下Java實(shí)現(xiàn)HTML轉(zhuǎn)Word的具體方法,感興趣的可以了解下2025-08-08

