Spring Security OAuth 2.0

靜守己心&笑淡浮華發表於2021-08-28

簡介

Spring Cloud Security Oauth2(SpringBoot版本的整合過於麻煩)就是將 OAuth 2.0 和 Spring Security 整合在一起,得到一套完整的安全解決方案,可以實現單點登入、令牌中繼、令牌交換等功能。

在 OAuth 2.0中,provider 角色事實上是把授權服務和資源服務分開,有時候它們也可能在同一個應用中,用 Spring Security OAuth 你可以選擇把它們分成兩個應用,當然多個資源服務可以共享同一個授權服務。獲取 token 的請求由 Spring MVC 的控制端點處理,訪問受保護的資源由標準的 Spring Security 請求過濾器處理。

使用

環境搭建

1、引入依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2、Spring Security 配置

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 強雜湊雜湊加密實現
     * @return BCryptPasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 解決 無法直接注入 AuthenticationManager
     * @return authenticationManagerBean
     * @throws Exception 異常資訊
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 禁用csrf
                .cors().and().csrf().disable()
                // 過濾請求
                .authorizeRequests()
                // 放行oauth認證介面
                .antMatchers("/login.html", "/oauth/**").permitAll()
                // 除上面外的所有請求全部需要鑑權認證,使用者登入後可訪問
                .anyRequest().authenticated();
    }

}

自定義使用者資訊

@Service
public class UserService implements UserDetailsService {

    private List<User> userList;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @PostConstruct
    public void initData() {
        String password = passwordEncoder.encode("123456");
        userList = new ArrayList<>();
        userList.add(new User("macro", password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")));
        userList.add(new User("andy", password, AuthorityUtils.commaSeparatedStringToAuthorityList("client")));
        userList.add(new User("mark", password, AuthorityUtils.commaSeparatedStringToAuthorityList("client")));
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<User> findUserList = userList.stream().filter(user -> user.getUsername().equals(username)).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(findUserList)) {
            return findUserList.get(0);
        } else {
            throw new UsernameNotFoundException("使用者名稱或密碼錯誤");
        }
    }
}

配置認證伺服器

@Configuration
// 開啟認證伺服器
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserService userService;

    /**
     * 使用密碼模式需要配置
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                // 通過記憶體儲存
                .inMemory()
                // 客戶端Id
                .withClient("client")
                // 祕鑰
                .secret("123456")
                // 配置訪問token的有效期
                .accessTokenValiditySeconds(3600)
                // 配置重新整理token的有效期
                .refreshTokenValiditySeconds(864000)
                // 配置redirect_uri,用於授權成功後跳轉
                .redirectUris("http://www.baidu.com")
                // 配置申請的許可權範圍
                .scopes("all")
                // 配置grant_type,表示授權型別,authorization_code:授權碼模式,password:密碼模式
                .authorizedGrantTypes("authorization_code", "password");
    }
}

配置資源伺服器

@Configuration
// 開啟資源伺服器
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            // 攔截所有請求
            .authorizeRequests().anyRequest().authenticated().and()
            // 配置需要保護的資源路徑
            .requestMatchers().antMatchers("/user/**");
    }
}

配置受保護資源

@RestController
public class UserController {

    @GetMapping("/user/getCurrentUser")
    public Object getCurrentUser(Authentication authentication) {
        // 返回自定義資源物件
        return authentication.getPrincipal();
    }

}

測試

授權碼模式

啟動服務後,在瀏覽器訪問該地址進行登入授權,http://localhost:9401/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all&state=normal

輸入賬號面後進行登入操作,示意圖如下:

image

登入後進行授權操作,示意圖如下:

image

授權操作之後,瀏覽器會帶著授權碼跳轉到我們指定的路徑(redirect_rul):https://www.baidu.com/code=eTsADY&state=normal ,此地址中 code 的值就是授權碼,使用授權碼請求該地址獲取訪問令牌:http://localhost:9401/oauth/token ,請求時需要攜帶以下引數(可以通過Postman進行偽造)

  • grant_type:authorization_code,授權碼模式

  • code:eTsADY,剛剛獲取到的

  • client_id:client,認證伺服器配置的

  • redirect_url:http://www.baidu.com ,認證後需要跳轉的地址

  • scope:all,許可權範圍

image

接下來通過在請求頭中新增訪問令牌,訪問需要登入認證的介面進行測試,發現已經可以成功訪問:http://localhost:9401/user/getCurrentUser ,到此,授權碼模式測試完成。

image


密碼模式

使用密碼請求該地址獲取訪問令牌:http://localhost:9401/oauth/token ,首先配置認證資訊,其次將 grant_type 修改為 password,即可成功獲取到需要認證的介面資料了。

image

image

相關文章