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

17 篇文章 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等信息了

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
非常感谢您的提问,以下是一个简单的 Spring Boot 集成 Spring Security OAuth2 的代码示例: @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private DataSource dataSource; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/login**", "/error**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/home") .permitAll() .and() .logout() .permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .jdbcAuthentication() .dataSource(dataSource) .usersByUsernameQuery("SELECT username, password, enabled FROM users WHERE username = ?") .authoritiesByUsernameQuery("SELECT username, authority FROM authorities WHERE username = ?"); } @Bean public TokenStore tokenStore() { return new JdbcTokenStore(dataSource); } @Bean public ClientDetailsService clientDetailsService() { JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource); clientDetailsService.setPasswordEncoder(passwordEncoder()); return clientDetailsService; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .tokenStore(tokenStore()) .authenticationManager(authenticationManagerBean()) .userDetailsService(userDetailsService); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients .withClient("client") .secret(passwordEncoder().encode("secret")) .authorizedGrantTypes("password", "refresh_token") .scopes("read", "write") .accessTokenValiditySeconds(3600) .refreshTokenValiditySeconds(86400); } } 请注意,这只是一个简单的示例,您需要根据您的实际需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值