Spring Security OAuth2 Demo —— 密碼模式(Password)


前情回顧

前幾節分享了OAuth2的流程與授權碼模式和隱式授權模式兩種的Demo,我們了解到授權碼模式是OAuth2四種模式流程最復雜模式,復雜程度由大至小:授權碼模式 > 隱式授權模式 > 密碼模式 > 客戶端模式

其中密碼模式的流程是:讓用戶填寫表單提交到授權服務器,表單中包含用戶的用戶名、密碼、客戶端的id和密鑰的加密串,授權服務器先解析並校驗客戶端信息,然后校驗用戶信息,完全通過返回access_token,否則默認都是401 http狀態碼,提示未授權無法訪問

本文目標

編寫與說明密碼模式的Spring Security Oauth2的demo實現,讓未了解過相關知識的讀者對此授權流程有個更直觀的概念

以下分成授權服務器與資源服務器分別進行解釋,只講關鍵部分,詳情見Github:https://github.com/hellxz/spring-security-oauth2-learn

搭建密碼模式授權服務器

代碼結構與之前兩個模式相同,這里便不再進行說明

SecurityConfig配置

package com.github.hellxz.oauth2.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("hellxz")
                .password(passwordEncoder().encode("xyz"))
                .authorities(new ArrayList<>(0));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //所有請求必須認證
        http.authorizeRequests().anyRequest().authenticated();
    }
}

基本的SpringSecurity的配置,開啟Spring Security的Web安全功能,填了一個用戶信息,所有資源必須經過授權才可以訪問

AuthorizationConfig授權服務器配置

package com.github.hellxz.oauth2.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
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;

@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;//密碼模式需要注入認證管理器

    @Autowired
    public PasswordEncoder passwordEncoder;

    //配置客戶端
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //@formatter:off
        clients.inMemory()
                .withClient("client-a")
                  .secret(passwordEncoder.encode("client-a-secret"))
                  .authorizedGrantTypes("password") //主要是這里,開始了密碼模式
                  .scopes("read_scope");
        //@formatter:on
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);//密碼模式必須添加authenticationManager
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .checkTokenAccess("isAuthenticated()");
    }
}

這里開啟了授權服務器的功能,與上幾篇文章中不同的是注入了AuthenticationManager,以及在configure(AuthorizationServerEndpointsConfigurer endpoints)方法中設置了AuthenticationManager

application.properties配置sever.port=8080

搭建資源服務器

這里的關鍵就是ResourceConfig,配置比較簡單與其它幾個模式完全一致,模式的不同主要表現在授權服務器與客戶端服務器上,資源服務器只做token的校驗與給予資源

package com.github.hellxz.oauth2.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
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.RemoteTokenServices;

@Configuration
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Primary
    @Bean
    public RemoteTokenServices remoteTokenServices() {
        final RemoteTokenServices tokenServices = new RemoteTokenServices();
        //設置授權服務器check_token端點完整地址
        tokenServices.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
        //設置客戶端id與secret,注意:client_secret值不能使用passwordEncoder加密!
        tokenServices.setClientId("client-a");
        tokenServices.setClientSecret("client-a-secret");
        return tokenServices;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        //設置創建session策略
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        //@formatter:off
        //所有請求必須授權
        http.authorizeRequests()
                .anyRequest().authenticated();
        //@formatter:on
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId("resource1").stateless(true);
    }
}

ResourceController主要接收一個用戶名,返回一個username與email的json串

application.properties設置server.port=8081

准備工作到這里就差不多了,開始測試

測試流程

這里客戶端使用postman手動發送請求進行演示

  • 訪問/oauth/token端點,獲取token

    http://localhost:8080/oauth/token?username=hellxz&password=xyz&scope=read_scope&grant_type=password

    請求頭:

    請求體:

  • token返回值

    {
        "access_token": "4a3c351d-770d-42aa-af39-3f54b50152e9",
        "token_type": "bearer",
        "expires_in": 43199,
        "scope": "read_scope"
    }
    
  • 使用token調用資源,訪問http://localhost:8081/user/hellxz001,注意使用token添加Bearer請求頭

    相當於在Headers中添加 Authorization:Bearer 4a3c351d-770d-42aa-af39-3f54b50152e9

  • 資源正確返回

尾聲

本文僅說明密碼模式的精簡化配置,某些部分如資源服務再訪問授權服務去校驗token這部分生產環境可能會換成Jwt、Redis等tokenStore實現,授權服務器中的用戶信息與客戶端信息生產環境應從數據庫中讀取,對應Spring Security的UserDetailsService實現類或用戶信息的Provider等

最近發現博客寫得相對較長,一方面有相當大的重復解釋代碼的部分,另一方面是代碼很多不關鍵的地方也直接全貼出來了,博客長了代碼全了,讀者容易失去閱讀的興趣與探索實踐的欲望。代碼不全會直接貼出源碼地址,暫時就這樣,把這幾篇關於OAuth2授權模式demo的文章趕出來

代碼早就寫完了,下周可能要開始加班了,先把這些已經完成的部分寫出來,后續有什么新的知識點才有時間記


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM