SpringBoot集成SpringSecurity5和OAuth2 — 1、基于内存的OAuth2认证服务器

论坛 期权论坛 脚本     
已经匿名di用户   2022-2-7 16:34   1773   0

相关代码:参考码云上的OAuth2【仅供本人参考使用,不对外,但跟着本文也可实现】

一、项目环境配置

1.1 、JDK8

1.2、Spring Boot: 2.3.0.RELEASE

1.3、spring-cloud.version:Hoxton.SR4

二、代码

1、主要的依赖

1.1 starter-web

  <!-- starter-web-->
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

1.2 OAuth2

  <!--OAuth2-->
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-oauth2</artifactId>
  </dependency>

1.3 springboot-test

  <!-- springboot-test-->
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
   <exclusions>
    <exclusion>
     <groupId>org.junit.vintage</groupId>
     <artifactId>junit-vintage-engine</artifactId>
    </exclusion>
   </exclusions>
  </dependency>

1.4 完整的pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.3.0.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
 </parent>
 <groupId>cn.iotspider</groupId>
 <artifactId>oauth2</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <name>oauth2</name>
 <description>Demo project for Spring Boot</description>

 <properties>
  <java.version>1.8</java.version>
  <spring-cloud.version>Hoxton.SR4</spring-cloud.version>
 </properties>

 <dependencies>

  <!-- starter-web-->
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <!--OAuth2-->
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-oauth2</artifactId>
  </dependency>

  <!-- springboot-test-->
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
   <exclusions>
    <exclusion>
     <groupId>org.junit.vintage</groupId>
     <artifactId>junit-vintage-engine</artifactId>
    </exclusion>
   </exclusions>
  </dependency>

 </dependencies>

 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
  </dependencies>
 </dependencyManagement>

 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
  </plugins>
 </build>

</project>

2、涉及到的文件

2.1 pom.xml文件

2.2 WebConfig 配置SpringSecurity

package cn.iotspider.oauth2.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
//对全部方法进行验证
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public PasswordEncoder passwordEncoder;

    /**
     * 配置用户登录验证服务
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       //未配置加密方式
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password("123456")
                .roles("ADMIN");

        //配置了BCryptPasswordEncoder的加密方式
//        auth.inMemoryAuthentication()
//                .passwordEncoder(passwordEncoder())
//                .withUser("admin")
//                .password(passwordEncoder().encode("123456"))
//                .roles("ADMIN");
    }

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

}

configure(AuthenticationManagerBuilder auth)方法里如果使用的是未配置加密方式的登录认证,则在登录时控制台会报

There is no PasswordEncoder mapped for the id "null",这是springsecurity5中新增的加密方式

package org.springframework.security.crypto.password 包里找到DelegatingPasswordEncoder类找到这个方法

查看在该类里的extractId()方法,发现密码需要一个用{}包裹的前缀;

    private String extractId(String prefixEncodedPassword) {
        if (prefixEncodedPassword == null) {
            return null;
        } else {
            int start = prefixEncodedPassword.indexOf("{");
            if (start != 0) {
                return null;
            } else {
                int end = prefixEncodedPassword.indexOf("}", start);
                return end < 0 ? null : prefixEncodedPassword.substring(start + 1, end);
            }
        }
    }

再到该类的 matches(CharSequence rawPassword, String prefixEncodedPassword)


    public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
        if (rawPassword == null && prefixEncodedPassword == null) {
            return true;
        } else {
            String id = this.extractId(prefixEncodedPassword);
            PasswordEncoder delegate = (PasswordEncoder)this.idToPasswordEncoder.get(id);
            if (delegate == null) {
                return this.defaultPasswordEncoderForMatches.matches(rawPassword, prefixEncodedPassword);
            } else {
                String encodedPassword = this.extractEncodedPassword(prefixEncodedPassword);
                return delegate.matches(rawPassword, encodedPassword);
            }
        }
    }

可以看到使用extractId()提取出来的id查询PasswordEncoder接口的加密方式对象,所以我们从这可以知道,在设置密码时就需要在密码前使用{}包裹加密方式。例 "{bcrypt}" + passwordEncoder().encode("123456");

所以只要将

.password("123456")

改为

.password("{bcrypt}" + passwordEncoder().encode("123456"))

即可;

    /**
     * 配置用户登录验证服务
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       //未配置加密方式
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password("123456")
                .password("{bcrypt}" + passwordEncoder().encode("123456"))
                .roles("ADMIN");
    }
WebConfig中的完整代码:
package cn.iotspider.oauth2.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
//对全部方法进行验证
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public PasswordEncoder passwordEncoder;

    /**
     * 配置用户登录验证服务
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       //未配置加密方式,控制台会报 There is no PasswordEncoder mapped for the id "null"
//        auth.inMemoryAuthentication()
//                .withUser("admin")
//                .password("123456")
//                .roles("ADMIN");


        //配置了BCryptPasswordEncoder的加密方式 (一),在passwordEncoder()中使用passwordEncoder()声明使用的加密方式
        auth.inMemoryAuthentication()
                .passwordEncoder(passwordEncoder())
                .withUser("admin")
                .password(passwordEncoder().encode("123456"))
                .roles("ADMIN");

        //配置了BCryptPasswordEncoder的加密方式 (二),在password()中使用{bcrypt}前缀声明使用的加密方式
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password("{bcrypt}" + passwordEncoder().encode("123456"))
                .roles("ADMIN");
    }

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

}

加密方式(一)和(二)使用其中一种方式即可。

2.3 OAuth2ServerConfig配置OAuth2认证服务器

需要实现 AuthorizationServerConfigurerAdapter

完整代码:

package cn.iotspider.oauth2.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;

import java.util.concurrent.TimeUnit;

@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public BCryptPasswordEncoder bCryptPasswordEncoder;

    @Bean
    public BCryptPasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 授权步骤相关的配置
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.inMemory()
                .withClient("client")
//                .secret("{bcrypt}"+bCryptPasswordEncoder.encode("secret"))
                .secret(bCryptPasswordEncoder.encode("secret"))
                .authorizedGrantTypes("authorization_code","client_credentials", "password", "refresh_token")
//                .authorizedGrantTypes("client_credentials", "password", "refresh_token")
                .scopes("all")
                .redirectUris("http://localhost:8080/callback")
                .accessTokenValiditySeconds(1200)
                .refreshTokenValiditySeconds(50000);
    }

    /**
     允许表单验证,浏览器直接发送post请求即可获取tocken;即允许使用POST方式发送获取token的请求
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
                "isAuthenticated()");
        oauthServer.allowFormAuthenticationForClients();
    }

}

配置好后启动springboot.

2.4登录获取授权码:http://localhost:8080/oauth/authorize?response_type=code&client_id=client&scope=all&redirect_uri=http://localhost:8080/callback

账号:admin

密码:123456

如果返回如下错误信息,通过信息可发现是无效的授权方式,也就是说没有配置使用授权码模式(Authorization code Grant)或 隐式授权模式(Implicit Grant),关于OAuth2的四种授权模式可阅读:OAuth2.0的四种授权模式,将第一个注释掉的authorizedGrantTypes放开并注释掉第二个authorizedGrantTypes,或者在authorizedGrantTypes中直接加入authorization_code即可。

OAuth Error
error="invalid_grant", error_description="A redirect_uri can only be used by implicit or authorization_code grant types."

重启后再次登录获取授权码,输入前面admin账号密码后,就能进入授权选择页面了

选择Approve,再点击Authorize按钮后,跳转的地址就变成了之前设置的重定向地址了,同时返回了code授权码

2.5获取token

使用PostMan,往http://localhost:8080/oauth/token发送post请求,http://localhost:8080/oauth/token?grant_type=authorization_code&code=y6jhAN&client_id=client&client_secret=secret&redirect_uri=http://localhost:8080/callback地址后面的都是请求需要携带的参数,发送后如果报错,且控制台又出现

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"这个错误,主要是因为OAuth2ServerConfig类中configure(ClientDetailsServiceConfigurer clients)方法里用的secret没有使用{}包裹的加密方式

secret(bCryptPasswordEncoder.encode("secret"))

改为

secret("{bcrypt}"+bCryptPasswordEncoder.encode("secret"))就可以了

后面就能获取到token等信息了

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:81
帖子:4969
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP