Keycloak通用接入手册(以Java为例)


第一步:在keycloak平台上,新建一个client app

 联系Keycloak管理员,提供 应用的 root url app name即可。

 建好client之后,可以得到一个 client secret(密匙)


第二步:在client project中 加入 keycloak配置

配置形如:

# 空间名,默认所有app和用户都在一个keycloak空间
keycloak.realm=ops
# keycloak服务器的auth地址
keycloak.auth-server-url=http://localhost:8180/auth
# client app name
keycloak.resource=fm-cache-cloud
# client secret
keycloak.credentials.secret=d4589683-0ce7-4982-bcd3-c48a12572f79
# 登录url和所需要的role
keycloak.securityConstraints[0].authRoles[0] = user
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /manage/ssologin/*

上面的配置,除了 最后一项,其他都是基本配置,直接填好就行了。

最后一项需要先在controller中定义这样一个用于登录的url,下面会讲。


在讲第三步之前,说一下Keycloak客户端接入原理:

(keycloak支持很多种方式接入,我只讲其中一部分)

基本原理

    keycloak是JBOSS开源的,JBOSS是做服务器的,所以,对于服务器,它比谁都玩得熟,Keycloak的强大之处也在于,它对于客户端应用的管控,直接可以到 服务器层面(相当于给服务器装一个插件,然后进入这个服务器的请求,都会被拦截和认证)。

    本文以我们常用的Spring Boot 内嵌的 Tomcat 服务器 为例,在项目中引入 keycloak包并配置好之后,实际上开启了一个 tomcat的 filter,这个filter会拦截指定的url,如果没登录,就跳转到统一登录页面进行登录。

    (如果不是用的Spring boot内嵌的tomcat服务器,比如用的是独立的tomcat服务器,原理也是一样的,只是配置方法不一样)

    keycloak提供了很多种插件(adapter),例如仅Java的adapter就有如下:

        2.1.1. Java Adapter Config

        2.1.2. JBoss EAP/WildFly Adapter

        2.1.4. JBoss Fuse 6、7 Adapter

        2.1.6. Spring Boot Adapter

        2.1.7. Tomcat 6, 7 and 8 Adapters

        2.1.8. Jetty 9.x Adapters

        2.1.9. Jetty 8.1.x Adapter

        2.1.10. Spring Security Adapter

        2.1.11. Java Servlet Filter Adapter

    

    下面以 Spring Boot Adapter为例,说明如何装插件。其他服务器,或者其他语言的客户端是类似的,很简单。

安装方法:

例如 spring boot 1.x,在pom.xml中引入下面依赖即可:

<!-- Keycloak  -->
<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-legacy-spring-boot-starter</artifactId>
    <version>5.0.0</version>
</dependency>

如果是spring boot 2.x版本,上面的 starter 换成 keycloak-spring-boot-starter。


由于keycloak是基于 filter拦截器的,所以如果 项目本身 已经用了filter来作为登录控制的话,则需要进行改造,Java项目常见情况如下:

1、基于shiro框架进行登录控制;

2、基于spring security进行登录控制;

3、基于自定义简单的servlet filter进行登录控制;

下面,针对 2、3 项目情况,说明如何进行集成配置(注意,不同的项目,情况可能不完全一样,只要掌握思路即可)。


第三步(针对“3、基于简单servlet filter登录的项目”)

改造之前:

  • 原项目,采用了filter来拦截请求,如果没登录,则跳转到登录页面(比如 /mange/login)。

  • 使用项目自带的登录页面,进行登录。


改造之后:

  • 沿用原来的filter,但是如果没登录,则跳转到 用于统一登录的指定controller(比如 /mange/ssologin);

  • 把这个统一登录的controller的url,配置成 keycloak拦截的登录地址,使用keycloak来进行登录;


这个controller,逻辑很简单,一个例子如下,流程见注释:

@GetMapping("/ssologin")
public View ssologin(HttpServletRequest request) {
    // 1、从request获取用户名,再查看本系统中有无此用户
    // 2、有这个用户,则执行登录成功逻辑,代表登录成功
    // 3、没有这个用户,则执行登录失败逻辑,比如跳转到登录页面
}

一个真实例子:

@RequestMapping(value = "/ssologin", method = RequestMethod.GET)
public ModelAndView ssologin(HttpServletRequest request, HttpServletResponse response) {
    // 从request获取用户名,再查看本系统中有无此用户
    String userName = Identity(request).getName();
    AppUser user = userService.getByName(userName);
    if (user == null) {
        return new ModelAndView("redirect:/manage/login");
    } else {
        // 有这个用户,则添加到session或者cookie中,代表登录成功
        userLoginService.addLoginStatus(request, response, user.getId().toString());
    }
    // 返回用户主页
    return new ModelAndView("redirect:/admin/app/list.do");
}


登录原理 说明:

由于在keycloak配置中加入了url权限控制,如下

# 登录url和所需要的role
keycloak...authRoles[0] = user
keycloak...patterns[0] = /manage/ssologin/*

那么,访问这个 url,在没登录的情况下,就会跳转到 统一登录页面,用户输入用户密码成功登录之后,就会进入到上面定义的 controller中,再执行应用本地的登录逻辑即可


退出登录,很简单,只需要执行  HttpServletRequest.logout() 即可

例如:

@GetMapping(value = "/logout")
public void logout(HttpServletRequest request) throws ServletException {
    // 先移除本地的session或者cookie
    userLoginService.removeLoginStatus(request, response);
    // 然后执行 request.logout() 即可
    request.logout();
}


第三步(针对“2、基于spring security进行登录的项目”)


改造之前:

  • 请求被spring security的UsernamePasswordAuthenticationFilter拦截,判断是否登录,如果未登录,则跳转到项目自己的登录页面。

  • 使用项目自带的登录页面,进行登录。


准备工作:写一个KeycloakAuthenticationFilter,重载spring security的UsernamePasswordAuthenticationFilter,它的逻辑是,先判断有没有用户进行统一登录,如果用户已经统一登录了,但是本地没登录,则进行本地登录。


改造之后:

  • 请求被spring security的KeycloakAuthenticationFilter拦截,判断是否登录,如果未登录,则跳转到 用于统一登录的指定controller(比如 /keycloak/ssologin);

  • 把这个统一登录的controller的url,配置成 keycloak拦截的登录地址,使用keycloak来进行登录;

  • keycloak登录的controller执行成功之后,再跳转到spring security的登录处理url进行登录。


改造之前,spring security配置如下

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable();
    http.headers().frameOptions().disable();
    http.authorizeRequests().antMatchers("/openapi/**", "/keycloak/**", "/img/**")
    .permitAll().antMatchers("/**").hasAnyRole(USER_ROLE);
    http.formLogin().loginPage("/signin").permitAll().loginProcessingUrl("/sslogin")
        .failureUrl("/signin?#/error").and().httpBasic();
    http.logout().invalidateHttpSession(true).clearAuthentication(true)
        .logoutSuccessUrl("/signin?#/logout");
    http.exceptionHandling().authenticationEntryPoint(
        new LoginUrlAuthenticationEntryPoint("/signin?#/logout"));
  }

改造之后,spring security配置如下:

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable();
    http.headers().frameOptions().disable();
    http.authorizeRequests().antMatchers("/openapi/**", "/keycloak/**", "/img/**")
    .permitAll().antMatchers("/**").hasAnyRole(USER_ROLE);
    http.formLogin().loginPage("/signin").permitAll().loginProcessingUrl("/sslogin")
        .failureUrl("/signin?#/error").and().httpBasic().and()
        // add by zollty
        .addFilterBefore(keycloakAuthenticationFilter(), BasicAuthenticationFilter.class);
    http.logout().invalidateHttpSession(true).clearAuthentication(true)
        .logoutSuccessUrl("/signin?#/logout")
        // add by zollty
        .addLogoutHandler(new KeycloakSpringLogoutHandler());
    http.exceptionHandling().authenticationEntryPoint(
        // to keycloak ssologin controller
        new LoginUrlAuthenticationEntryPoint("/keycloak/ssologin"));
  }

即,加了一个自定义的 keycloakAuthenticationFilter 和 KeycloakSpringLogoutHandler,同时 将LoginUrlAuthenticationEntryPoint登录地址,修改成 用于统一登录的指定controller的URL。这个controller代码如下:

@RequestMapping(value = "keycloak/ssologin", method = RequestMethod.GET)
public ModelAndView ssologin() {

    return new ModelAndView("redirect:/sslogin");
}

进入到这个方法,代表已经sso登录成功,然后直接跳转到 spring security的loginProcessingUrl进行本地登录即可。

KeycloakSpringLogoutHandler的代码如下:

public class KeycloakSpringLogoutHandler implements LogoutHandler {


    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response
                        , Authentication authentication) {
        
        // 退出keycloak sso
        try {
            request.logout();
        } catch (ServletException e) {
            e.printStackTrace();
        }
        
    }

}

其作用是退出统一登录。

KeycloakAuthenticationFilter的代码如下:

public class KeycloakAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    
    static final String DEFAULT_PASSWD_SIGN = "`kc`";

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, 
            HttpServletResponse response) throws AuthenticationException {
     
        String username = obtainUsername(request);
        String password = null;
        
        Identity identity = new Identity(request);
        if (username == null && identity.getSecurityContext() != null) {
            username = identity.getName();
            password = DEFAULT_PASSWD_SIGN;
        } else {
            password = obtainPassword(request);
            if (password == null) {
                password = "";
            } else if(DEFAULT_PASSWD_SIGN.equals(password)) {
                throw new AuthenticationServiceException("Auth error");
            }
        }
        

        username = username.trim();

        UsernamePasswordAuthenticationToken authRequest = 
            new UsernamePasswordAuthenticationToken(username, password);

        setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);
    }

}

这个代码来源于spring security的UsernamePasswordAuthenticationFilter,只是加了7行代码,用于先判断是否有用户已经统一登录过,如果已经统一登录了,则将其密码设为一个特殊字符,这是一个取巧的做法,因为用户已经统一登录了,所以后面只要看到是这个特殊字符,就不再验证密码,直接登录。


其他编程语言应用的接入


方法一:自己根据OpenID Connect和OAuth2.0的原理,找到相应的client,引入自己的项目使用。

官方指导文档:https://www.keycloak.org/docs/latest/securing_apps/index.html#other-openid-connect-libraries


方法二:直接在网上或GitHub上搜索现成的第三方Client、Adapter或Demo,参照配置。例如:

1)Keycloak Golang相关的第三方adapter:

https://github.com/mitch-strong/KeycloakGo

https://github.com/cwocwo/keycloak-adapter-go

更多参见:https://github.com/search?l=Go&q=Keycloak&type=Repositories

2)Python相关的Client、Adapter或Demo:(包括Django、Flask相关的例子都有)

https://github.com/search?l=Python&q=Keycloak&type=Repositories


全文总结

    在有keycloak adapter的加持下,keycloak的接入相当简单,它是可以做到不改一行代码的 —— 之所以上面提到一些小的改动,完全是 为了 替换或者 适配 原来项目已有 的登录filter配置,把原来的账号+密码的登录形式,拦截并跳转到 keycloak统一登录,登录完成之后,再在本地项目进行登录。

    明白这个原理之后,其他类型的项目都是一样的处理逻辑。

    具体接入时,参见这个文档 securing apps guide,说得很清楚。

    另外,参考其GitHub上的 Quick-Start Demo


附 Keycloak官网: www.keycloak.org


Special专题:关于现代化前端、移动端的接入说明

    参见这篇文章:Keycloak现代化前端、移动端的接入说明


© 2009-2020 Zollty.com 版权所有。渝ICP备20008982号