前面介绍了前端应用如何集成Keycloak实现统一身份验证、权限控制,可参考 vue-element-admin集成Keycloak实现统一身份验证、权限控制 。如果对Keycloak还不太了解的话,可以参考Keycloak快速上手指南对Keycloak的基本概念进行了解。本文将讲述典型的Spring Boot/Spring Security服务端的应用如何集成Keycloak,以实现SSO登录、统一身份验证、权限控制等功能。
服务端的web类型的应用,最常见的就是提供web页面服务以及Restful API。不同类型的应用,Keycloak后台创建的配置会稍微有些区别。下面将创建3个客户端:
spring-boot-keycloak-web srping-boot-keycloak-security-api spring-boot-keycloak-webapi
之前的文章已经说明过,这里再列一下,Keycloak目前的访问类型共有3种:
confidential :适用于服务端应用,且需要浏览器登录以及需要通过密钥获取 access token 的场景。典型的使用场景就是服务端渲染的web系统。
public :适用于客户端应用,且需要浏览器登录的场景。典型的使用场景就是前端web系统,包括采用vue、react实现的前端项目等。
bearer-only :适用于服务端应用,不需要浏览器登录,只允许使用 bearer token 请求的场景。典型的使用场景就是restful api。
分别创建用户admin、customer,及角色ROLE_ADMIN、ROLE_CUSTOMER,并进行绑定
Spring Boot集成keycloak依赖
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
<version>${keycloak.version}</version>
</dependency>
复制代码
如需使用Spring Security,则添加如下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
复制代码
这里以同时提供web页面及Restful API服务的项目spring-boot-keycloak-webapi的配置为例,来看下怎么进行keycloak相关的配置
server:
port: 8083
keycloak:
realm: demo
auth-server-url: http://127.0.0.1:8080/auth
resource: spring-boot-keycloak-webapi
ssl-required: external
credentials:
secret: d50a059a-484b-4138-8403-22a491fbc488
use-resource-role-mappings: false
bearer-only: false
autodetect-bearer-only: true
security-constraints:
- authRoles:
- ROLE_CUSTOMER
securityCollections:
- name: customer
patterns:
- /customer
- authRoles:
- ROLE_ADMIN
securityCollections:
- name: admin
patterns:
- /admin
复制代码
realm :Keycloak后台对应的realm
auth-server-url :Keycloak的地址
resource :Keycloak后台创建的对应的Client
credentials.secret :Keycloak添加客户端后Credentials Tab内对应的内容
use-resource-role-mappings :使用realm级别还是应用级别的角色控制
bearer-only :应用的Keycloak访问类型是bearer-only设置为true,否则设为false
autodetect-bearer-only :应用同时提供web页面跟Restful API服务时需设置为true,Keycloak会根据请求的方式,将未通过认证的请求重定向到登录页或者直接返回 401 状态码
security-constraints :针对不同的路径定义相应的角色以实现权限管理,如果是集成Spring Security,则不需要此配置,改为在Spring Security相关的配置中控制
更多的配置控制可以查阅官方文档: Keycloak官方Java适配器配置
autodetect-bearer-only 机制说明 官方文档对于 autodetect-bearer-only 的说明比较含糊
Keycloak auto-detects SOAP or REST clients based on typical headers like X-Requested-With , SOAPAction or Accept .
针对HTTP请求头到底取什么样的值才被识别为是API类型的请求,其实并没有描述的很清楚。我们只能找到相应的源码来具体看下,实际实现到底是怎么样的。
源码位于 keycloak-adapter-core jar包中的 RequestAuthenticator 抽像类中
protected boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
if (!deployment.isAutodetectBearerOnly()) return false;
String headerValue = facade.getRequest().getHeader("X-Requested-With");
if (headerValue != null && headerValue.equalsIgnoreCase("XMLHttpRequest")) {
return true;
}
headerValue = facade.getRequest().getHeader("Faces-Request");
if (headerValue != null && headerValue.startsWith("partial/")) {
return true;
}
headerValue = facade.getRequest().getHeader("SOAPAction");
if (headerValue != null) {
return true;
}
List<String> accepts = facade.getRequest().getHeaders("Accept");
if (accepts == null) accepts = Collections.emptyList();
for (String accept : accepts) {
if (accept.contains("text/html") || accept.contains("text/*") || accept.contains("*/*")) {
return false;
}
}
return true;
}
复制代码
通过上面这段源码,我们就能很清晰的知道,在 autodetect-bearer-only 配置设置为true时,HTTP请求头需满足以下几种情况,才被认为是API类型请求,这种情况下未通过认证直接返回 401 状态码而不是重定向到登录页
X-Requested-With 请求头的值为 XMLHttpRequest Faces-Request 请求头的值以 partial/ 开头 SOAPAction 请求头 Accept 的请求头的值,不能包含 text/html 、 text/* 、 */* 这些值 如需集成Spring Security,则Spring Boot配置中的 security-constraints 可以删除,使用代码进行如下配置
@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
/**
* Read Keycloak config from spring boot config file
*/
@Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
/**
* Defines the session authentication strategy.
*/
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/customer/**").hasRole("CUSTOMER")
.antMatchers("/admin/**").hasAnyRole("ADMIN")
.anyRequest().permitAll();
}
}
复制代码
@RequestMapping(value = "/", method = RequestMethod.GET)
public String index() {
return "index";
}
@RequestMapping(value = "/customer", method = {RequestMethod.GET, RequestMethod.POST})
public String customer(HttpServletRequest request) {
KeycloakSecurityContext keycloak = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
return "customer :: " + keycloak.getTokenString();
}
@RequestMapping(value = "/admin", method = {RequestMethod.GET, RequestMethod.POST})
public String admin(HttpServletRequest request) {
KeycloakSecurityContext keycloak = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
return "admin :: " + keycloak.getTokenString();
}
@RequestMapping(value = "/logout", method = {RequestMethod.GET, RequestMethod.POST})
public String logout(HttpServletRequest request) {
try {
request.logout();
return "logout success";
} catch (ServletException e) {
LOGGER.error("keycloak logout error", e);
return "logout fail";
}
}
复制代码
运行同时提供web页面及Restful API服务的项目spring-boot-keycloak-webapi,看下相关的效果