在这篇文章中,我们将讨论如何使用Spring Boot Security OAuth2保护REST API。我们将为不同的crud操作实现AuthorizationServer,ResourceServer和一些REST API,并使用Postman测试这些API。在这里我们将使用mysql数据库读取用户证书而不是内存认证。另外,为了简化我们的ORM解决方案,我们将使用spring-data和BCryptPasswordEncoder进行密码编码。
OAuth只是一个安全的授权协议,它处理第三方应用程序授权访问用户数据而不暴露其密码。例如。(在许多网站上用fb,gPlus,twitter进行登录..)都在这个协议下工作。当你知道涉及的各方时,协议变得更容易。基本上有三方参与:oAuth提供者,oAuth客户端和所有者。这里,oAuth提供者提供了诸如Facebook,Twitter之类的身份验证令牌。同样,oAuth客户端是希望代表所有者访问凭证的应用程序,所有者是拥有oAuth提供程序(例如facebook和twitter)的帐户的用户。
OAuth 2是一种授权框架,它使应用程序能够获得有限访问HTTP服务(如Facebook,GitHub和DigitalOcean)上的用户帐户的权限。它的工作方式是将用户身份验证委派给承载用户帐户的服务,并授权第三方应用程序访问该用户帐户。OAuth 2为Web和桌面应用程序以及移动设备提供授权流程。
以下是OAuth2定义的4种不同的授权类型
授权码(Authorization Code):与服务器端应用程序一起使用
隐式(Implicit):用于移动应用程序或Web应用程序(在用户设备上运行的应用程序)
资源所有者密码凭证(Resource Owner Password Credentials):与受信任的应用程序(如服务本身拥有的应用程序)一起使用
客户端证书(Client Credentials):与应用程序API访问一起使用
以下是Spring Boot Security OAuth2实现的项目结构。
以下是所需的依赖关系。
<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>
这个类扩展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);
}
}
我们的上下文中的资源是我们为粗暴操作公开的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。
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;
}
}
以下是在应用程序启动时插入的插入语句。
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.
以下设置。
一旦你提出请求,你会得到如下结果。它有访问令牌和刷新令牌。
无令牌 访问资源使用令牌访问资源使用刷新令牌刷新令牌
通常情况下,oAuth2的令牌到期时间非常少,您可以使用以下API在令牌过期时刷新令牌。
我可以在评论部分看到,大多数读者遇到了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来获得授权令牌。以下是截图。
没有客户端身份验证。尝试添加适当的认证过滤器。
{
"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的安全。
https://www.leftso.com/article/382.html