Spring Boot Security OAuth2 例子(Bcrypt Encoder)

编程教程 > Java > Spring (8835) 2024-11-26 14:39:04

引言

在这篇文章中,我们将讨论如何使用Spring Boot Security OAuth2保护REST API。我们将为不同的crud操作实现AuthorizationServerResourceServer和一些REST API,并使用Postman测试这些API。在这里我们将使用mysql数据库读取用户证书而不是内存认证。另外,为了简化我们的ORM解决方案,我们将使用spring-data和BCryptPasswordEncoder进行密码编码。

什么是OAuth

      OAuth只是一个安全的授权协议,它处理第三方应用程序授权访问用户数据而不暴露其密码。例如。(在许多网站上用fb,gPlus,twitter进行登录..)都在这个协议下工作。当你知道涉及的各方时,协议变得更容易。基本上有三方参与:oAuth提供者,oAuth客户端和所有者。这里,oAuth提供者提供了诸如Facebook,Twitter之类的身份验证令牌。同样,oAuth客户端是希望代表所有者访问凭证的应用程序,所有者是拥有oAuth提供程序(例如facebook和twitter)的帐户的用户。

 

什么是OAuth2

OAuth 2是一种授权框架,它使应用程序能够获得有限访问HTTP服务(如Facebook,GitHub和DigitalOcean)上的用户帐户的权限。它的工作方式是将用户身份验证委派给承载用户帐户的服务,并授权第三方应用程序访问该用户帐户。OAuth 2为Web和桌面应用程序以及移动设备提供授权流程。

 

OAuth2角色

OAuth2提供4种不同的角色。

  • Resource Owner: 用户
  • Client: 接入应用
  • Resource Server: API
  • Authorization Server: API

OAuth2授权类型

以下是OAuth2定义的4种不同的授权类型

授权码(Authorization Code):与服务器端应用程序一起使用

隐式(Implicit):用于移动应用程序或Web应用程序(在用户设备上运行的应用程序)

资源所有者密码凭证(Resource Owner Password Credentials):与受信任的应用程序(如服务本身拥有的应用程序)一起使用

客户端证书(Client Credentials):与应用程序API访问一起使用

项目结构

以下是Spring Boot Security OAuth2实现的项目结构。
以下是Spring Boot Security OAuth2实现的项目结构。
 

Maven的依赖

pom.xml

以下是所需的依赖关系。

<parent> 
        <groupId> org.springframework.boot </ groupId> 
        <artifactId> spring-boot-starter-parent </ artifactId> 
        <version> 1.5.8.RELEASE </ version> 
</ parent> 
	
    <dependencies> 
	    <dependency > 
                   <groupId> org.springframework.boot </ groupId> 
                   <artifactId> spring-boot-starter-web </ artifactId> 
            </ dependency> 
	    <dependency> 
                   <groupId> org.springframework.boot </ groupId>
                   <artifactId> spring-boot-starter-data-jpa</ artifactId> 
             </ dependency> 
	     <dependency> 
                    <groupId> org.springframework.boot </ groupId> 
                    <artifactId> spring-boot-starter-security </ artifactId> 
	      </ dependency> 
		  <dependency> 
                   <groupId> org.springframework .boot </ groupId> 
                   <artifactId> spring-security-oauth2 </ artifactId> 
             </ dependency> 
	     <dependency> 
                    <groupId> mysql </ groupId> 
                    <artifactId>mysql-connector-java </ artifactId>
	      </ dependency> 
	      <dependency> 
                     <groupId> commons-dbcp </ groupId> 
                     <artifactId> commons-dbcp </ artifactId> 
		</ dependency> 
		
    </ dependencies>


 

OAuth2授权服务器配置

这个类扩展AuthorizationServerConfigurerAdapter并负责生成特定于客户端的令牌。假设,如果用户想通过Facebook 登录到devglan.com,那么facebook auth服务器将为Devglan生成令牌。在这种情况下,Devglan成为客户端,它将成为代表用户从脸书 - 授权服务器请求授权代码。以下是Facebook将使用的类似实现。

在这里,我们使用内存凭证,client_id作为devglan-client,CLIENT_SECRET作为devglan-secret。但您也可以自由使用JDBC实现。

@EnableAuthorizationServer:启用授权服务器。AuthorizationServerEndpointsConfigurer定义授权和令牌端点以及令牌服务。

AuthorizationServerConfig.java:
 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.provider.token.TokenStore;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

	static final String CLIEN_ID = "devglan-client";
	static final String CLIENT_SECRET = "devglan-secret";
	static final String GRANT_TYPE = "password";
	static final String AUTHORIZATION_CODE = "authorization_code";
	static final String REFRESH_TOKEN = "refresh_token";
	static final String IMPLICIT = "implicit";
	static final String SCOPE_READ = "read";
	static final String SCOPE_WRITE = "write";
	static final String TRUST = "trust";
	static final int ACCESS_TOKEN_VALIDITY_SECONDS = 1*60*60;
    static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 6*60*60;
	
	@Autowired
	private TokenStore tokenStore;

	@Autowired
	private AuthenticationManager authenticationManager;

	@Override
	public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {

		configurer
				.inMemory()
				.withClient(CLIEN_ID)
				.secret(CLIENT_SECRET)
				.authorizedGrantTypes(GRANT_TYPE_PASSWORD, AUTHORIZATION_CODE, REFRESH_TOKEN, IMPLICIT )
				.scopes(SCOPE_READ, SCOPE_WRITE, TRUST)
				.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS).
				refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS);
	}

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints.tokenStore(tokenStore)
				.authenticationManager(authenticationManager);
	}
}


 

OAuth2资源服务器配置

我们的上下文中的资源是我们为粗暴操作公开的REST API。要访问这些资源,必须对客户端进行身份验证。在实时场景中,每当用户尝试访问这些资源时,都会要求用户提供他真实性,一旦用户被授权,他将被允许访问这些受保护的资源。

@EnableResourceServer:启用资源服务器
ResourceServerConfig.java:
 

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.error.OAuth2AccessDeniedHandler;

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

	private static final String RESOURCE_ID = "resource_id";
	
	@Override
	public void configure(ResourceServerSecurityConfigurer resources) {
		resources.resourceId(RESOURCE_ID).stateless(false);
	}

	@Override
	public void configure(HttpSecurity http) throws Exception {
        http.
                anonymous().disable()
                .authorizeRequests()
                .antMatchers("/users/**").authenticated()
                .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
	}

}

安全配置

这个类扩展了WebSecurityConfigurerAdapter并提供了通常的spring安全配置。这里,我们使用bcrypt编码器来编码我们的密码。您可以尝试使用此在线Bcrypt工具对密码进行编码和匹配。以下配置基本引导了授权服务器和资源服务器。

@EnableWebSecurity:启用弹簧安全Web安全支持。

@EnableGlobalMethodSecurity:支持方法级别访问控制,如@PreAuthorize @PostAuthorize

在这里,我们正在使用inmemorytokenstore,但您可以自由使用JdbcTokenStore或JwtTokenStore.Here

SecurityConfig.java:
 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.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.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import javax.annotation.Resource;

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

    @Resource(name = "userService")
    private UserDetailsService userDetailsService;

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

    @Autowired
    public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(encoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .anonymous().disable()
                .authorizeRequests()
                .antMatchers("/api-docs/**").permitAll();
    }

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }

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

    @Bean
    public FilterRegistrationBean corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
        bean.setOrder(0);
        return bean;
    }
}

Rest API

以下是我们为测试目的而暴露的非常基本的REST API。

UserController.java
 

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value="/user", method = RequestMethod.GET)
    public List listUser(){
        return userService.findAll();
    }

    @RequestMapping(value = "/user", method = RequestMethod.POST)
    public User create(@RequestBody User user){
        return userService.save(user);
    }

    @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
    public String delete(@PathVariable(value = "id") Long id){
        userService.delete(id);
        return "success";
    }

}

现在让我们定义负责从数据库中获取用户详细信息的用户服务。接下来是Spring将用来验证用户的实现。
UserServiceImpl.java:
 

@Service(value = "userService")
public class UserServiceImpl implements UserDetailsService, UserService {
	
	@Autowired
	private UserDao userDao;

	public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
		User user = userDao.findByUsername(userId);
		if(user == null){
			throw new UsernameNotFoundException("Invalid username or password.");
		}
		return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthority());
	}

	private List getAuthority() {
		return Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"));
	}

	public List findAll() {
		List list = new ArrayList<>();
		userDao.findAll().iterator().forEachRemaining(list::add);
		return list;
	}
}

默认数据库SQL脚本

以下是在应用程序启动时插入的插入语句。

INSERT INTO User (id, username, password, salary, age) VALUES (1, 'Alex123', '$2a$04$I9Q2sDc4QGGg5WNTLmsz0.fvGv3OjoZyj81PrSFyGOqMphqfS2qKu', 3456, 33);
INSERT INTO User (id, username, password, salary, age) VALUES (2, 'Tom234', '$2a$04$PCIX2hYrve38M7eOcqAbCO9UqjYg7gfFNpKsinAxh99nms9e.8HwK', 7823, 23);
INSERT INTO User (id, username, password, salary, age) VALUES (3, 'Adam', '$2a$04$I9Q2sDc4QGGg5WNTLmsz0.fvGv3OjoZyj81PrSFyGOqMphqfS2qKu', 4234, 45);


 

测试应用程序

运行Application.java作为Java应用程序。我们将使用邮递员来测试OAuth2实现。

生成AuthToken:在头文件中,我们有用户名和密码分别为Alex123和密码作为Authorization头。按照Oauth2规范,Access token请求应该使用application/x-www-form-urlencoded.以下设置。
设置postmain工具的HTTP请求头部、
一旦你提出请求,你会得到如下结果。它有访问令牌和刷新令牌。
请求测试结果

无令牌 访问资源使用令牌访问资源使用刷新令牌刷新令牌

无令牌访问返回

有令牌返回

通常情况下,oAuth2的令牌到期时间非常少,您可以使用以下API在令牌过期时刷新令牌。
5

 

常见错误

我可以在评论部分看到,大多数读者遇到了2个错误。因此,添加这个部分最好能帮助读者。

访问此资源需要完整身份验证
 

{
"timestamp": 1513747665246,
"status": 401,
"error": "Unauthorized",
"message": "Full authentication is required to access this resource",
"path": "/oauth/token"
}


 

如果您错过了在POST的授权部分添加用户名/密码的情况,则会导致此问题。在此部分中,选择类型为基本身份验证,并提供凭证作为devglan-client和devglan-secret,然后向url - http://localhost:8080/oauth/token来获得授权令牌。以下是截图。
常见错误举证1

 

没有客户端身份验证。尝试添加适当的认证过滤器。

{
    "error": "unauthorized",
    "error_description": "There is no client authentication. Try adding an appropriate authentication filter."
}

在这种情况下,检查你的auth url.It应该是 - http://localhost:8080/oauth/token而不是http://localhost:8080/oauth/token/

缺少授权类型
在这种情况下,您错过了在请求中添加grant_type。请尝试将其添加为password

总结

在本教程中,我们了解了如何通过实现资源服务器和授权服务器来保护REST API与OAUTH2的安全。

提示:项目源码下载 spring-boot-security-oauth2-master.zip


评论
User Image
提示:请评论与当前内容相关的回复,广告、推广或无关内容将被删除。

相关文章
引言在这篇文章中,我们将讨论如何使用Spring Boot Security OAuth2保护REST API
本文主要翻译spring官方的基于spring security框架的oauth2开发指南,spring,oauth2,spring框架,Java编程
spring boot 入门之security oauth2 jwt完美整合例子,Java编程中spring boot框架+spring security框架+spring security o...
使用OAuth2安全的Spring REST API,Secure Spring REST API using OAuth2(含demo代码下载)
引言在本文中,我们将讨论有关Spring启动安全性和JWT令牌的OAUTH2实现以及保护REST API
RFC 6749OAuth 2.0 的标准是 RFC 6749 文件
spring boot 2.0 security 5.0 整合,实现自定义表单登录。spring boot 2.0框架使用。
环境JDK 17Spring Boot 3.2.1-3.2.3Spring Security 6.2.1-6.3.1Spring Security 权限/角色常
前言使用Spring Boot 3 Security 6.2 JWT 完成无状态的REST接口认证和授权管理。环境JDK 17Spring Boot 3.3.2
Spring Boot 2.0 Redis整合,通过spring boot 2.0整合Redis作为spring缓存框架的实现。
Spring Boot 2.1 新特性,已升级Spring 版本为5.1,支持servlet 4.0,支持Tomcat 9.0等等
Spring Boot 2.0 有哪些新特性_Spring Boot 2.0新功能,在本文中,我们将探讨为Spring Boot 2.0计划的一些更改和功能。我们还会描述这些变化如何帮助我们提高...
Spring Boot 2.0,Spring框架的Spring Boot 中的Spring Boot Actuator变化讲解。并且了解如何在Spring Boot 2.0中使用Actuator...
1. 什么是数据源?spring boot 2 数据源 DataSource(数据源) 是连接到任何物理数据库的工厂. DriverManager 工具的替代方案