Spring Security整合KeyCloak保护Rest API实现详解
简介
本篇文章主要介绍如何使用Spring Security整合KeyCloak保护Rest API。
前置条件
在开始本文之前,你应该已经了解过以下知识:
- Spring Boot
- Spring Security
- Rest API设计基础
- OAuth2.0和OpenID Connect协议的基本知识
同时,在开始之前,你应该已经完成以下准备工作:
- 下载并安装KeyCloak
- 创建一个Spring Boot项目
实现步骤
步骤一:引入KeyCloak依赖
在 pom.xml
文件中添加以下依赖:
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
<version>15.0.2</version>
</dependency>
步骤二:配置KeyCloak客户端
在 application.yml
文件中添加以下配置:
keycloak:
auth-server-url: <KeyCloak服务器地址>
realm: <Realm名称>
resource: <客户端名称>
credentials:
secret: <客户端密码>
步骤三:配置Spring Security
在 WebSecurityConfig
类中添加以下代码:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* 密码加密方式
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* 配置安全过滤器链
*/
@Bean
public KeycloakConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/public/**").permitAll()
.antMatchers(HttpMethod.POST, "/api/public/**").permitAll()
.antMatchers(HttpMethod.PUT, "/api/public/**").permitAll()
.anyRequest().authenticated();
}
}
代码解释:
- 继承
KeycloakWebSecurityConfigurerAdapter
后,Spring Security 就可以通过KeyCloak进行身份验证了。 configureGlobal
方法用来设置身份验证的方式。在这里,我们使用KeyCloak身份验证。configure
方法用来配置安全过滤器链。这里我们配置了一个公开的API和一个安全的API。公开的API不需要进行身份验证。
步骤四:测试
我们可以通过以下代码来测试我们的API:
@RestController
@RequestMapping("/api")
public class TestController {
@GetMapping("/public/hello")
public String publicHello() {
return "Hello, Public!";
}
@GetMapping("/hello")
public String hello() {
return "Hello, Protected!";
}
}
其中,/api/public/hello
是公开的API,不需要进行身份验证。/api/hello
是需要进行身份验证的API。
我们可以使用 httpie
工具来测试:
$ http GET localhost:8080/api/public/hello
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Date: Thu, 28 Oct 2021 07:57:20 GMT
Transfer-Encoding: chunked
Hello, Public!
$ http GET localhost:8080/api/hello
HTTP/1.1 401
Set-Cookie: KC_RESTART=eyJhbGciOi...[省略]...Sjh-RA; Path=/auth/realms/Test/account/;
WWW-Authenticate: Bearer realm="Test", error="unauthorized", error_description="Bearer token is not valid"
Content-Length: 0
可以看到,公开的API返回了 Hello, Public!
而安全的API返回了401错误。
步骤五:使用Postman进行测试
我们可以使用Postman工具来测试我们的API。首先,我们需要获取到一个AccessToken。我们可以使用以下代码来获取AccessToken:
@Service
public class KeyCloakServiceImpl implements KeyCloakService {
@Autowired
private KeycloakSpringBootProperties properties;
@Autowired
private KeycloakClientRequestFactory factory;
@Override
public AccessTokenResponse getAccessToken(String username, String password) throws IOException, KeycloakServiceException {
String accessTokenUrl = properties
.getAuthServerUrl()
.concat("/realms/")
.concat(properties.getRealm())
.concat("/protocol/openid-connect/token");
HttpClient httpClient = factory.createHttpClient();
HttpPost httpPost = new HttpPost(accessTokenUrl);
List<NameValuePair> formParams = Arrays.asList(
new BasicNameValuePair("client_id", properties.getResource()),
new BasicNameValuePair("username", username),
new BasicNameValuePair("password", password),
new BasicNameValuePair("grant_type", "password")
);
httpPost.setEntity(new UrlEncodedFormEntity(formParams));
HttpResponse response = httpClient.execute(httpPost);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new KeycloakServiceException("Failed to get access token");
}
ObjectMapper mapper = new ObjectMapper();
String result = EntityUtils.toString(response.getEntity());
return mapper.readValue(result, AccessTokenResponse.class);
}
}
其中, AccessTokenResponse
是一个包含AccessToken等信息的DTO类。
现在我们已经获得了AccessToken了,我们可以使用Postman工具来测试我们的API。我们首先需要在Postman中设置相应的Header:
Authorization
:BearerAccessToken
然后我们就可以使用Postman工具来测试我们的API了。可以看到,公开的API返回了 Hello, Public!
而安全的API返回了401错误。
示例1:如何在Spring Security中使用自定义身份验证方式
有时候我们需要使用自定义的身份验证方式来替代KeyCloak进行身份验证。这里我们举一个例子,介绍如何实现该功能。
假设我们有一个API需要使用自定义身份验证方式。这个API是 /api/custom/hello
,具有如下代码:
@RestController
@RequestMapping("/api/custom")
public class CustomController {
@GetMapping("/hello")
public String hello() {
return "Hello, Custom!";
}
}
我们需要使用带有 Authorization
头的请求进行测试:
$ http GET localhost:8080/api/custom/hello
HTTP/1.1 401
WWW-Authenticate: Custom
Content-Length: 0
可以看到该API返回了401错误,表示该API需要进行身份验证。
我们可以使用以下代码来实现自定义身份验证:
@Configuration
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/custom/**").authenticated()
.and()
.addFilterBefore(new CustomAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
public static class CustomAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader != null && authorizationHeader.startsWith("Custom ")) {
filterChain.doFilter(request, response);
return;
}
response.setHeader("WWW-Authenticate", "Custom");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
}
代码解释:
- 我们通过
configure
方法来配置安全过滤器链。进行自定义身份验证的API需要进行身份验证。 - 我们通过
addFilterBefore
方法来添加我们自定义的身份验证过滤器。该过滤器会在Spring Security的默认身份验证过滤器之前执行。 CustomAuthenticationFilter
类是自定义的身份验证过滤器。它会检查请求头中是否包含Authorization: Custom
。如果包含,则表示身份验证成功;否则返回401错误,并在响应头中包含WWW-Authenticate: Custom
字段。
现在我们重新发起请求:
$ http GET localhost:8080/api/custom/hello
HTTP/1.1 401
WWW-Authenticate: Custom
Content-Length: 0
可以看到API返回了401错误,但是响应头中包含了 WWW-Authenticate: Custom
字段。这意味着我们已经成功地进行了自定义身份验证。
示例2:如何获取用户信息
在实际开发中,我们通常需要获取当前登录用户的信息。我们可以使用以下代码:
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/user")
public ResponseEntity<?> user(Authentication authentication) {
if (authentication == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("User not found");
}
return ResponseEntity.ok(authentication.getPrincipal());
}
}
其中,我们可以通过注入 Authentication
对象来获取当前登录用户的信息。如果没有登录,则返回401错误。
总结
本文介绍了如何使用Spring Security整合KeyCloak保护Rest API。同时,我们还介绍了如何使用自定义身份验证方式和如何获取用户信息。希望能对大家有所帮助。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring Security整合KeyCloak保护Rest API实现详解 - Python技术站