當前位置:
首頁 > 知識 > OAuth2 Provider的最簡單實現 (Spring Boot + Spring Security OAuth2)

OAuth2 Provider的最簡單實現 (Spring Boot + Spring Security OAuth2)

不是客戶端,而是服務端。

版本:

Spring Boot 1.4.3、Spring Security OAuth2 2.0.12

OAuth2.0的開源 Server / Client 實現可以參考這裡:https://oauth.net/code/,這裡採用Spring Security OAuth2實現四種授權模式中最常用的:Authorization Code Grant。具體可以看OAuth2.0標準的定義:https://tools.ietf.org/html/rfc6749#section-4.1。只為演示OAuth2.0整個過程,做最小實現。

Spring Security OAuth2默認提供的四個URL:

  • /oauth/authorize : 授權AuthorizationEndpoint
  • /oauth/token : 令牌TokenEndpoint
  • /oauth/check_token : 令牌校驗CheckTokenEndpoint
  • /oauth/confirm_access : 授權頁面WhitelabelApprovalEndpoint
  • /oauth/error : 錯誤頁面WhitelabelErrorEndpoint

pom.xml


org.springframework.boot
spring-boot-starter-web


org.springframework.boot
spring-boot-starter-security


org.springframework.security.oauth
spring-security-oauth2

Application.java

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

}

Config.java

public class Config {

public static final String OAUTH_CLIENT_ID = "oauth_client";
public static final String OAUTH_CLIENT_SECRET = "oauth_client_secret";
public static final String RESOURCE_ID = "my_resource_id";
public static final String SCOPES = { "read", "write" };

@Configuration
@EnableAuthorizationServer
static class OAuthAuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory
.withClient(OAUTH_CLIENT_ID)
.secret(OAUTH_CLIENT_SECRET)
.resourceIds(RESOURCE_ID)
.scopes(SCOPES)
.authorities("ROLE_USER")
.authorizedGrantTypes("authorization_code", "refresh_token")
.redirectUris("http://default-oauth-callback.com")
.accessTokenValiditySeconds(60*30) // 30min
.refreshTokenValiditySeconds(60*60*24); // 24h
}
}

@Configuration
@EnableResourceServer
static class OAuthResourceConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests
.antMatchers(HttpMethod.GET, "/api/**").access("#oauth2.hasScope("read")")
.antMatchers(HttpMethod.POST, "/api/**").access("#oauth2.hasScope("write")");
}
}

@Configuration
@EnableWebSecurity
static class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication
.withUser("user").password("123").roles("USER")
.and
.withUser("admin").password("123").roles("ADMIN");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf.disable;
http.authorizeRequests
.antMatchers("/oauth/authorize").authenticated
.and
.httpBasic.realmName("OAuth Server");
}
}

}

Controller.java

@RestController
public class Controller {

@GetMapping("/api/get")
public String get {
return "Hello World!";
}

@PostMapping("/api/post")
public String post {
return "POST process has finished.";
}

@GetMapping("/api/user")
public Object get(HttpServletRequest req) {
SecurityContextImpl sci = (SecurityContextImpl) req.getSession.getAttribute("SPRING_SECURITY_CONTEXT");
if (sci != null) {
Authentication authentication = sci.getAuthentication;
if (authentication != null) {
return authentication.getPrincipal;
}
}
return "none";
}

}

Test.java

public class Test {

public static void main(String[] args) {
System.out.println(generate("oauth_client", "oauth_client_secret"));
}

private static String generate(String clientId, String clientSecret) {
String creds = String.format("%s:%s", new Object[] { clientId, clientSecret });
try {
return "Basic " + new String(Base64.encode(creds.getBytes("UTF-8")));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Could not convert String");
}
}

}

【Run As】【Spring Boot App】啟動伺服器後,看到以下Log:

引用

Mapped "{[/oauth/authorize]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.authorize(java.util.Map,java.util.Map,org.springframework.web.bind.support.SessionStatus,java.security.Principal)

Mapped "{[/oauth/authorize],methods=[POST],params=[user_oauth_approval]}" onto public org.springframework.web.servlet.View org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.approveOrDeny(java.util.Map,java.util.Map,org.springframework.web.bind.support.SessionStatus,java.security.Principal)

Mapped "{[/oauth/token],methods=[POST]}" onto public org.springframework.http.ResponseEntity org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(java.security.Principal,java.util.Map) throws org.springframework.web.HttpRequestMethodNotSupportedException

Mapped "{[/oauth/token],methods=[GET]}" onto public org.springframework.http.ResponseEntity org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.getAccessToken(java.security.Principal,java.util.Map) throws org.springframework.web.HttpRequestMethodNotSupportedException

Mapped "{[/oauth/check_token]}" onto public java.util.Map org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint.checkToken(java.lang.String)

Mapped "{[/oauth/confirm_access]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.WhitelabelApprovalEndpoint.getAccessConfirmation(java.util.Map,javax.servlet.http.HttpServletRequest) throws java.lang.Exception

Mapped "{[/oauth/error]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.WhitelabelErrorEndpoint.handleError(javax.servlet.http.HttpServletRequest)

(1)授權請求(Get)

URL:http://localhost:8080/oauth/authorize?client_id=oauth_client&scope=read&response_type=code&state=rensanning&redirect_uri=http://default-oauth-callback.com

由於對/oauth/authorize開啟了HTTP Basic認證,所以需要輸入密碼:

OAuth2 Provider的最簡單實現 (Spring Boot + Spring Security OAuth2)

輸入正確用戶名密碼(user/123)後顯示授權頁:

OAuth2 Provider的最簡單實現 (Spring Boot + Spring Security OAuth2)

選擇Approve點擊Authorize按鈕後,自動跳轉到 http://default-oauth-callback.com?code=sdb6vF&state=rensanning

URL中的code參數值即為授權碼,該值是一個6位英數字的隨機數,具體可以看源碼:org.springframework.security.oauth2.common.util.RandomValueStringGenerator.generate

!!授權碼10分鐘過期目前還沒有實現!! https://github.com/spring-projects/spring-security-oauth/issues/725

(2)獲得令牌(Post)

URL:http://localhost:8080/oauth/token?grant_type=authorization_code&redirect_uri=http://default-oauth-callback.com&code=sdb6vF

HTTP的header需要clientId和clientSecret的提供Base64值。具體可以執行Test.java獲取。

Authorization: Basic b2F1dGhfY2xpZW50Om9hdXRoX2NsaWVudF9zZWNyZXQ=

OAuth2 Provider的最簡單實現 (Spring Boot + Spring Security OAuth2)

返回令牌信息

引用

{

"access_token": "1cc5ffbd-faac-4d20-afd9-b8531acd248e",

"token_type": "bearer",

"refresh_token": "5b319fed-5600-4ea2-8c4f-61f6e3ea6e41",

"expires_in": 1631,

"scope": "read"

}

生成的令牌是一個UUID,具體可以看源碼:org.springframework.security.oauth2.provider.token.DefaultTokenServices.createAccessToken

(4)訪問API(Get)

直接訪問API返回401。URL:http://localhost:8080/api/get

OAuth2 Provider的最簡單實現 (Spring Boot + Spring Security OAuth2)

通過access_token參數訪問。URL:http://localhost:8080/api/get?access_token=1cc5ffbd-faac-4d20-afd9-b8531acd248e

OAuth2 Provider的最簡單實現 (Spring Boot + Spring Security OAuth2)

通過http header參數訪問。URL:http://localhost:8080/api/get

Authorization: Bearer 1cc5ffbd-faac-4d20-afd9-b8531acd248e

OAuth2 Provider的最簡單實現 (Spring Boot + Spring Security OAuth2)

*** @EnableResourceServer 自動增加OAuth2AuthenticationProcessingFilter過濾器

*** !!SpringBoot1.5 @EnableResourceServer和@EnableWebSecurity配置的HttpSecurity有先後順序的問題,需要特殊設置!!參考:

https://github.com/spring-projects/spring-security-oauth/issues/993#issuecomment-284430752

https://stackoverflow.com/questions/29893602/spring-security-form-logging-and-outh2-in-same-app

(5)刷新令牌(Post)

URL:http://localhost:8080/oauth/token?grant_type=refresh_token&refresh_token=5b319fed-5600-4ea2-8c4f-61f6e3ea6e41

Authorization: Basic b2F1dGhfY2xpZW50Om9hdXRoX2NsaWVudF9zZWNyZXQ=

返回新的access_token:

"access_token": "3cbe70fc-753f-44ff-9bb4-0ba6bc3c9aab",

"expires_in": 1800,

OAuth2 Provider的最簡單實現 (Spring Boot + Spring Security OAuth2)

舊的Token就不能再用了:

OAuth2 Provider的最簡單實現 (Spring Boot + Spring Security OAuth2)

通過新的Token訪問API:

OAuth2 Provider的最簡單實現 (Spring Boot + Spring Security OAuth2)

同理,可以測試scope為write的許可權!

參考:

http://projects.spring.io/spring-security-oauth/docs/oauth2.html

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 科技優家 的精彩文章:

C++學習(三)入門篇——函數
設計模式解密(10)-迭代器模式
架構之路 之 Nginx實現負載均衡
阿里巴巴2018屆應屆生在線編程測驗-研發工程師C/C++

TAG:科技優家 |

您可能感興趣

Spring security + oauth2.0 + redis + mybatis plus 搭建微服務
Spring Cloud Security+OAuth2+SSO快速上手-客戶端
理解OAuth2.0授權
與第三方應用對接技術文檔,怎麼能不知道OAuth2.0?