keycloak项目代码

文章介绍了如何使用Keycloak在两个Vue项目中实现一键登录功能,包括授权码模式的流程、前端Vue代码实现、后端SpringBoot代码实现以及Java代码调用KeycloakAPI进行用户数据操作。项目1和2分别有不同的登录验证方式,后端通过KeycloakAPI处理登录状态和用户信息。
摘要由CSDN通过智能技术生成

1.前言

有两个项目,需要用keycloak管理做一键登录(登录一个项目,另一个无需登录可直接访问)

项目1:管理系统(主业务)

项目2:文件上传下载(辅助业务)

授权码模式:(5条消息) keycloak“授权码模式”的运行流程(个人理解)_凯尔萨厮的博客-CSDN博客

2.keycloak前端Vue代码实现(授权码模式的①)

参照:

项目1:前端vue引入keycloak代码(访问项目未登录会跳转到keycloak等登录页)

备注:项目1登出后项目2会认证登录失效(刷新页面才会提示失效)

前提:

执行命令:【npm install @dsb-norge/vue-keycloak-js】引入JS

package.json { dependencies:"vue": "vue": "2.6.12", devDependencies:"vue-template-compiler": "2.6.12"} vue版本需要2.6起才支持keycloak.js

import keycloak from '@dsb-norge/vue-keycloak-js'

Vue.use(keycloak, {
  init: {
    onLoad: 'login-required', // 登录页面认证
    checkLoginIframe: false
  },
  config: {
    // url: 'http:localhost:8080/',    // keycloak 20.1版本 url
    url: 'http:localhost:8080/auth/',  // keycloak 15.0版本 url
    realm: 'test_realm_1', // keycloak 域
    clientId: 'test_client_1' // keycloak 客户端
  },
  onReady: (keycloak) => {
    keycloak.loadUserProfile().success((data) => {
      new Vue({
        router,
        store,
        render: h => h(App)
      }).$mount('#app')
    })
  }
})

登陆后,获取refreshToken

this.$keycloak.refreshToken

退出登录

this.$keycloak.logoutFn()

项目2:前端vue引入keycloak代码(访问项目未登录,手动指定登录到keycloak的登录页)

备注:项目2登出后项目1会认证登录失效(刷新页面才会提示失效)

import keycloak from "@dsb-norge/vue-keycloak-js";

Vue.use(keycloak, {
  init: {
    onLoad: "check-sso", // 只做验证,没有登录不跳转登录页面
    checkLoginIframe: false,
  },
  config: {
    // url: "http://localhost:8080",      // keycloak 20.1版本
    url: "http://localhost:8080/auth/"    // keycloak 15.0版本
    realm: "test_realm_2",    // 项目2的 域
    clientId: "test_client_2",// 项目2的 客户端
  },
  onReady(keycloak) {
    new Vue({
      router,
      keycloak,
      render: (h) => h(App),
    }).$mount("#app");
  },
});

判断是否做过登录

import Vue from "vue";
    
if (Vue.prototype.$keycloak.authenticated) {
  // 做过认证登录
} else {
  // 没做过认证,获取keycloak的登录页面url
  const loginUrl = Vue.prototype.$keycloak.createLoginUrl();
  // 跳转到keyclaok的登录页面
  window.location.replace(loginUrl);
}

项目3:调用API接口访问keycloak(无论登录登出,无法让项目1项目2同时登录登出)

备注:无法一件登录原因(猜测,axios数据异步处理,没有在浏览器生成Cookie)所以浏览器访问项目1项目2都不认为登陆过,这种方式只适用于前端没有token清空下获取keycloak用户信息

前端直接通过API获取

import axios from 'axios'
import qs from 'qs'

const url = 'http://localhost:8080/realms/test_clien_3/protocol/openid-connect/token'
// keycloak 15.0版 8080后/realms前要加 /auth
const params = {
  client_id: 'test_clien_3',         // keycloak的客户端
  username: this.loginForm.username, // 登录用户名
  password: this.loginForm.password, // 登录密码
  grant_type: 'password'             // 固定值
}
const jsonParams = qs.stringify(params) // 参数要做转换
axios.post(url, jsonParams).then(res => {
  // keycloak访问成功可获取到用户信息
}).catch((error) => {
  // 异常处理
})

3.keycloak后端Springboot代码实现(授权码模式的④、⑤)

项目1:后端代码

application.properties

keyCloak-baseUrl=http://localhost:8080
keyCloak-realms=test_realm_1
keyCloak-client=test_client_1
keyCloak-accessTokenUrl=${keyCloak-baseUrl}/realms/${keyCloak-realms}/protocol/openid-connect/token
keyCloak-userInfoUrl=${keyCloak-baseUrl}/realms/${keyCloak-realms}/protocol/openid-connect/userinfo

# 15.0版keycloak 地址栏/realms前需要加 /auth

keycloak对象类

@Data
public class AuthenticationToken {
  String access_token;
  String expires_in;
  String refresh_expires_in;
  String refresh_token;
  String token_type;
  String session_state;
  String scope;
}

用refreshToken刷新accessToken

public String getAccessToken(String refreshToken) {
  MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
  params.add("grant_type", "refresh_token");
  params.add("refresh_token", refreshToken);
  params.add("client_id", PropertiesUtil.client);

  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
  headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

  HttpEntity<MultiValueMap<String, String>> reqEntity = new HttpEntity<>(params, headers);

  RestTemplate restTemplate = new RestTemplate();

  ResponseEntity<AuthenticationToken> authToken = 
    restTemplate.exchange(PropertiesUtil.accessTokenUrl,
    HttpMethod.POST, reqEntity, AuthenticationToken.class);

  return authenticationToken.getBody().getAccess_token();
}

用accessToken取用户信息

public static UserInfo getUserInfo(String accessToken) {

  RestTemplate restTemplate = new RestTemplate();

  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
  headers.setContentType(MediaType.APPLICATION_JSON);
  headers.setBearerAuth(accessToken);

  HttpEntity<MultiValueMap<String, String>> reqEntity = new HttpEntity<>(headers);
  ResponseEntity<Object> userInfo = 
    restTemplate.exchange(PropertiesUtil.userInfoUrl, HttpMethod.POST, reqEntity, Object.class);
  Map<String, Object> userInfoMap = (Map<String, Object>) userInfo.getBody();
  return getUserInfo(userInfoMap);
}

// 解析返回的用户信息
private static UserInfo getUserInfo(Map<String, Object> userInfoMap) {
  UserInfo userInfo = new UserInfo();
  Set<String> keys = userInfoMap.keySet();
  for (String key : keys) {
    switch (key) {
      case "error":
        throw new Exception("");
      case "sub":
        userInfo.setId((String)keycloakUserInfoMap.get(key));
        break;
      case "preferred_username":
        userInfo.setLoginId((String)keycloakUserInfoMap.get(key));
        break;
      case "name":
        userInfo.setName((String)keycloakUserInfoMap.get(key));
        break;
      case "email":
        userInfo.setEmail((String)keycloakUserInfoMap.get(key));
        break;
      case "resource_access":
        Map<String, Object> clients = (Map<String, Object>)userInfoMap.get(key);
        if (clients == null) {
          break;
        }
  Map<String, Object> roles = (Map<String, Object>)clients.get(PropertiesUtil.client);
        if (roles == null) {
          break;
        }
        // 用户角色信息(名称)
        List<String> userRoles = (List<String>)roles.get("roles");
        break;
      default:
        break;
    }
  }
  return userInfo;
}

项目2:后端代码

同项目1一样,后端都是调用keycloakAPI的形式访问Keycloak

为了避免页面刷新才能识别keycloak用户登出,后端拦截器加了访问Keycloak处理,accessToken访问失败会返回异常code,前端判断异常code返回keycloak登录页面。

4.后端JAVA代码调用keycloak的API操作数据

(1). pom.xml引入keycloak依赖

 (2). application.properties追加值

keyCloak-adminName=admin

keyCloak-adminPassword=123456

keycloak.auth-server-url=http://localhost:8080/auth

keycloak.realm=${keyCloak-realms}

keycloak.resource=${keyCloak-client}

(3). 新建操作用户数据的DTO

import lombok.Data;

@Data
public class KeycloakUserDto {

  /**
   * 账号
   */
  private String userCode;

  /**
   * 密码
   */
  private String password;

  /**
   * 名
   */
  private String firstName;

  /**
   * 姓
   */
  private String lastName;

  /**
   * 邮箱
   */
  private String mailAddress;

}

(4). 处理数据的共通类

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.ws.rs.core.Response;
import KeycloakUserDto;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.KeycloakBuilder;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;

/**
 */
public class KeycloakUtil {

  /**
   * keycloak登录数据成功返回的code
   */
  private static final int CREATE_OK = 201;

  /**
   * keycloak服务器连接
   */
  private static Keycloak server() {
    return KeycloakBuilder.builder()
      .serverUrl(KeycloakPropertiesUtil.baseUrl)
      .realm("master")
      .grantType(OAuth2Constants.PASSWORD)
      .clientId("admin-cli")
      .username(KeycloakPropertiesUtil.adminUserName)
      .password(KeycloakPropertiesUtil.adminUserPassword)
      .resteasyClient(new ResteasyClientBuilder().connectionPoolSize(1).build())
      .build();
  }

  /**
   * Keycloak作成用户
   * @param dto 用户情报
   * @return 结果 true:成功 false:失败
   */
  public static boolean createUser(KeycloakCreateUserDto dto) {
    // 开启服务器连接
    try (Keycloak kcServer = server()) {
      // 作成用户数据格式
      UserRepresentation user = editCreateUserInfo(dto);
      // 登录用户
      Response res = kcServer.realm(KeycloakPropertiesUtil.realms).users().create(user);
      关闭服务器
      kcServer.close();
      // 返回结果
      return res.getStatus() == CREATE_OK;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return false;
  }

  /**
   * Keycloak为用户赋予客户端权限
   * @param userCode 账号
   * @param clientRole 客户端权限名
   * @return 结果 true:成功 false:失败
   */
  public static boolean updateUserClientRole(String userCode, String clientRole) {
    // 开启服务器连接
    try (Keycloak kcServer = server()) {
      RealmResource realm = kcServer.realm(KeycloakPropertiesUtil.realms);
      ClientsResource clients = realm.clients();
      UsersResource users = realm.users();
      // 通过账号取用户(正常只会有一个)
      List<UserRepresentation> sameNameClientUser = users.search(userCode);
      for (UserRepresentation user : sameNameClientUser) {
        // 取客户端
        List<ClientRepresentation> sameNameClient = clients.findByClientId(KeycloakPropertiesUtil.client);
        for (ClientRepresentation client : sameNameClient) {
          // 取客户端权限
          RoleRepresentation role = clients.get(client.getId()).roles().get(clientRole).toRepresentation();
          // 为用户设定客户端权限
          List<RoleRepresentation> roles = new ArrayList<>();
          roles.add(role);
          users.get(user.getId()).roles().clientLevel(client.getId()).add(roles);
        }
      }
      kcServer.close();
      return true;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return false;
  }

  /**
   * 变更密码
   * @param userCode 账号
   * @param password 密码
   * @return 结果 true:成功 false:失败
   */
  public static boolean updatePassword(String userCode, String password) {
    CredentialRepresentation ps = editPasswordInfo(password);
    // 连接服务器
    try (Keycloak kcServer = server()) {
      // 密码变更
   kcServer.realm(KeycloakPropertiesUtil.realms).users().get(userCode).resetPassword(ps);
      return true;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return false;
  }

  /**
   * 删除用户
   * @param userCode 账号
   * @return 処理結果 true:成功 false:失敗
   */
  public static boolean deleteUser(String userCode) {
    // 连接服务器
    try (Keycloak kcServer = server()) {
      UsersResource users = kcServer.realm(KeycloakPropertiesUtil.realms).users();
      // 查询用户
      List<UserRepresentation> sameNameClientUser = users.search(userCode);
      for (UserRepresentation user : sameNameClientUser) {
      // 删除用户  
      users.delete(user.getId());
      }
    } catch (Exception e) {
      return false;
    }
    return true;
  }

  /**
   * 编辑用户信息
   */
  private static UserRepresentation editCreateUserInfo(KeycloakCreateUserDto dto) {
    UserRepresentation user = new UserRepresentation();
    user.setUsername(dto.getUserCode());
    user.setCredentials(Collections.singletonList(editPasswordInfo(dto.getPassword())));
    user.setFirstName(dto.getFirstName());
    user.setLastName(dto.getLastName());
    user.setEmail(dto.getMailAddress());
    user.setEnabled(true);
    return user;
  }

  /**
   * 编辑密码信息
   */
  private static CredentialRepresentation editPasswordInfo(String password) {
    CredentialRepresentation ps = new CredentialRepresentation();
    ps.setType(CredentialRepresentation.PASSWORD);
    ps.setValue(password);
    ps.setTemporary(false);
    return ps;
  }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值