前面介绍了前端应用如何集成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,看下相关的效果