Spring Security 自定义授权服务器实践记录
本文将详细讲解如何使用Spring Security自定义授权服务器,并提供两个示例说明。
前置条件
在开始学习本文前,需要准备以下环境:
- JDK1.8或以上版本
- Maven 3.0或以上版本
- Spring Boot 2.0或以上版本
配置依赖
首先,需要在pom.xml中添加以下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
</dependencies>
配置授权服务器
接下来,需要在应用程序中配置授权服务器。可以通过继承AuthorizationServerConfigurerAdapter
类来配置授权服务器。在这里,需要重写几个方法:
configure(ClientDetailsServiceConfigurer clients)
- 用于配置客户端详情信息;configure(AuthorizationServerEndpointsConfigurer endpoints)
- 用于配置授权(authorization)和令牌(token)的访问端点和令牌服务(token services);configure(AuthorizationServerSecurityConfigurer security)
- 用于配置令牌端点(token endpoint)的安全约束,即哪些请求将会被保护起来。
下面是一个示例:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client_id")
.secret("client_secret")
.scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(3600)
.refreshTokenValiditySeconds(86400*7);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.tokenEnhancer(jwtAccessTokenConverter);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
}
配置资源服务器
接下来需要配置资源服务器。在应用程序中,可以使用@EnableResourceServer
注解来开启资源服务器,并实现ResourceServerConfigurer
接口来配置资源服务器。
下面是一个示例:
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/api/**").authenticated();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("resource_id").stateless(true);
}
}
在这里,configure(HttpSecurity http)
方法会配置请求的授权规则,configure(ResourceServerSecurityConfigurer resources)
方法会配置资源服务器的ID。
示例1:密码模式
在密码模式中,用户需要在登录页面输入用户名和密码。应用程序会通过OAuth2协议向授权服务器发起请求,以获取访问令牌(access token)。在使用访问令牌访问资源时,资源服务器会验证令牌的有效性,并返回相应的资源。
配置用户和角色
首先,需要配置一个用户和角色:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().formLogin().loginPage("/login").permitAll()
.and().logout().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
在这里,通过@Bean
注解,配置了一个密码编码器(passwordEncoder()
方法)和一个身份验证管理器(authenticationManagerBean()
方法)。同时,在configure(AuthenticationManagerBuilder auth)
方法中,将userDetailsService
注入到AuthenticationManagerBuilder
中,以便在身份验证过程中使用。
请求认证
接下来,在应用程序中,需要创建一个控制器来接收用户名和密码并向授权服务器请求访问令牌。这里,需要使用AuthorizationCodeAccessTokenProvider
来进行请求。
@RestController
public class AuthController {
@Autowired
private AuthorizationServerTokenServices tokenServices;
@Autowired
private ClientDetailsService clientDetailsService;
@RequestMapping("/oauth/token")
public OAuth2AccessToken getToken(
@RequestParam Map<String, String> parameters,
HttpServletRequest request) throws ServletException {
String grantType = parameters.get("grant_type");
String clientId = parameters.get("client_id");
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
TokenRequest tokenRequest = new TokenRequest(parameters, clientId, clientDetails.getScope(),
grantType);
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request,
null);
OAuth2AccessToken token = tokenServices.createAccessToken(oAuth2Authentication);
return token;
}
}
在这里,通过@Autowired
注解,注入了AuthorizationServerTokenServices
和ClientDetailsService
。在getToken
方法中,通过parameters
中的grant_type
和client_id
来获取clientDetails
和tokenRequest
。然后,使用AuthorizationServerTokenServices
来创建一个访问令牌(OAuth2AccessToken
)。
发布资源
最后,发布一个资源,以便访问。在这里,发布了一个API,可以在使用访问令牌后访问。
@RestController
@RequestMapping("/api")
public class ApiController {
@GetMapping("/hello")
public String hello() {
return "Hello World!";
}
}
测试认证
启动应用程序,并访问http://localhost:8080/login
,输入用户名和密码来进行身份验证。然后,使用下面的curl
命令来向授权服务器请求访问令牌:
curl -X POST -u client_id:client_secret "http://localhost:8080/oauth/token?grant_type=password&username=user&password=password"
如果请求成功,将会返回如下JSON格式的响应:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
"token_type": "bearer",
"expires_in": 3599,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
"scope": [
"read",
"write"
]
}
使用获取到的访问令牌,通过下面的curl
命令来访问API:
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" "http://localhost:8080/api/hello"
如果请求成功,将会返回如下响应:
Hello World!
示例2:客户端模式
在客户端模式中,客户端向授权服务器发起请求,以获取访问令牌。在使用访问令牌访问资源时,资源服务器会验证令牌的有效性,并返回相应的资源。
配置客户端
首先,需要在授权服务器中配置客户端,并为其分配client_id
和client_secret
。下面是一个示例:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client_id")
.secret("client_secret")
.scopes("read", "write")
.authorizedGrantTypes("client_credentials")
.accessTokenValiditySeconds(3600);
}
//其他方法
}
在这里,通过clients.inMemory()
方法创建一个InMemoryClientDetailsService实例,并为其添加一个名为client_id
的客户端。该客户端使用client_credentials
授权方式,并且授权范围为read
和write
。
请求认证
然后,需要编写一个发送鉴权请求的客户端。下面是一个示例:
@Component
public class AuthClient {
private RestTemplate restTemplate;
private String url = "http://localhost:8080/oauth/token";
@Value("${client.id}")
private String clientId;
@Value("${client.secret}")
private String clientSecret;
public AuthClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String getAccessToken() {
HttpHeaders headers = new HttpHeaders();
String auth = clientId + ":" + clientSecret;
byte[] encodedAuth = Base64.encodeBase64(
auth.getBytes(Charset.forName("US-ASCII")));
String authHeader = "Basic " + new String(encodedAuth);
headers.set("Authorization", authHeader);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "client_credentials");
HttpEntity<MultiValueMap<String, String>> entity = new
HttpEntity<>(map, headers);
ResponseEntity<AccessToken> response = restTemplate.exchange(url, HttpMethod.POST, entity,
AccessToken.class);
return response.getBody().getAccessToken();
}
}
在这里,通过@Value
注解,注入了client_id
和client_secret
。在getAccessToken
方法中,创建一个包含请求头和请求体的HttpEntity
对象,并使用此对象向授权服务器发出请求,以获取访问令牌。
发布资源
最后,在资源服务器中发布一个API,以便访问。同时,在该API中使用@PreAuthorize
注解来添加安全性。下面是一个示例:
@RestController
@RequestMapping("/api")
public class ApiController {
@PreAuthorize("hasAuthority('read')")
@GetMapping("/hello")
public String hello() {
return "Hello World!";
}
}
在这里,使用@PreAuthorize
注解,添加了一个安全性规则,只有拥有read
权限的用户才能访问该API。
测试认证
启动应用程序,然后通过下面的curl
命令来向授权服务器请求访问令牌:
curl -X POST -u client_id:client_secret "http://localhost:8080/oauth/token?grant_type=client_credentials"
如果请求成功,将会返回如下JSON格式的响应:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
"token_type": "bearer",
"expires_in": 3599,
"scope": [
"read",
"write"
]
}
使用获取到的访问令牌,通过下面的curl
命令来访问API:
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" "http://localhost:8080/api/hello"
如果请求成功,将会返回如下响应:
Hello World!
结论
本文详细讲解了如何使用Spring Security自定义授权服务器,包括配置授权服务器和资源服务器,并提供了两个示例来说明如何使用密码模式和客户端模式进行认证。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring Security 自定义授权服务器实践记录 - Python技术站