SpringBoot微服务集成keycloak实现跨平台统一认证授权

发布时间 2023-03-31 18:37:43作者: java从精通到入门
// 项目架构 微服务划分:
// auth认证微服务 实现登录认证拦截,获取token
// gateway 网关微服务
// user用户微服务 用户权限管理
// system系统微服务 核心逻辑处理 
// xxx其他微服务
// common模块


//1、 common模块引入keycloak认证相关依赖
    <properties>
        <keycloak.version>15.0.2</keycloak.version>
    </properties>

    <dependencies>
        <!--SpringCloud OAuth2-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.1.RELEASE</version>
            <exclusions>
                <exclusion>
                    <artifactId>bcprov-jdk15on</artifactId>
                    <groupId>org.bouncycastle</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>HdrHistogram</artifactId>
                    <groupId>org.hdrhistogram</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </dependency>

        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-admin-client</artifactId>
            <version>${keycloak.version}</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-security-adapter</artifactId>
            <version>${keycloak.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.httpcomponents</groupId>
                    <artifactId>httpclient</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
            <version>${keycloak.version}</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.keycloak.bom</groupId>
                <artifactId>keycloak-adapter-bom</artifactId>
                <version>${keycloak.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

// 这里还引入了Spring OAuth2的相关依赖 由于之前系统用的Spring security oauth2做的认证
// 现在将其改为keycloak认证,keycloak对oauth2的兼容很好,通过配置可以轻松实现两种认证随意切换

//2、 auth微服务引入common模块,并添加相关配置:
application.yml:

#keycloak认证配置 (keycloak系统配置)
keycloak:
  auth-server-url: http://127.0.0.1:8411/auth/
  resource: admin-cli
  realm: test #keycloak的realm,最好新建一个,不建议使用master
  principal-attribute: preferred_username
  use-resource-role-mappings: false #// 是否启用keycloak用户角色映射 可以不启用,使用系统的用户角色映射
  ssl-required: none

#keycloak初始化配置(自定义配置*)
kc:
  master-realm-user-name: master管理员账号
  master-realm-user-password: master管理员密码
  target-realm: master
#获取token相关配置 (自定义配置*)
oauth:
  tokenUrl: http://127.0.0.1:8411/auth/realms/{替换成自己的域}/protocol/openid-connect/token
  kcClientId: test 客户端id
  kcClientSecret: 客户端secret

 


 

//自定义配置,建议写在配置文件里,也可以写死在代码里 。此时auth微服务已经集成了keycloak

//3、获取token

/**
 * keyCloak客户端安全配置 这里可以兼容oauth2
 */
@KeycloakConfiguration
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
//@ConditionalOnProperty(value = "oauthModel.type", havingValue = KEY_CLOAK, matchIfMissing = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {


    //放开获取token的接口
    String[] PERMIT_URL = {"/oauth/**","/keycloak/oauth/token"};


    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
        // adding proper authority mapper for prefixing role with "ROLE_"
        grantedAuthorityMapper.setPrefix("ROLE_");
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public org.keycloak.adapters.KeycloakConfigResolver keycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http
                .cors()
                .and()
                .csrf()
                .disable()
                .authorizeRequests()
                .antMatchers(PERMIT_URL)
                .permitAll()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                // 异常处理
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
                .and()
                .httpBasic();

    }

}


// 获取token可以有多种方式实现,keycloak已经内置了/oath/token接口获取token,
// 不过对已有系统兼容可能不太友好,会有重定向,跨域之类的问题
// 这里提供两种自定义获取token的方法,也为了可以兼容oauth2获取token
// 3.1、通过过滤器实现登录接口拦截,获取token 

/**
 * @Description: 登录接口拦截
 * @Author: zengzhengfu
 * @Date: 2021/4/13
 */
@Slf4j
@WebFilter(filterName = "LoginFilter", urlPatterns = "/oauth/token")
public class LoginFilter implements Filter {
    private final IAuthService iAuthService;

    @Override
    public void destroy() {

    }

    public LoginFilter(IAuthService iAuthService) {
        this.iAuthService = iAuthService;
    }

    @Override
    public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
        String requestUri = ((HttpServletRequest) arg0).getRequestURI();
        log.info("当前请求接口: " + requestUri);
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader bufferedReader = null;

        if (!MediaType.APPLICATION_JSON_VALUE.equals(arg0.getContentType())) {
            throw new ApplicationException("Request Headers ContentType错误: " + arg0.getContentType());
        }

        BodyReaderRequestWrapper wrapper = new BodyReaderRequestWrapper((HttpServletRequest) arg0);
        try {
            inputStream = wrapper.getInputStream();
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error("", e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    log.error("", e);
                }
            }
        }
        HashMap hashMap = JSON.parseObject(sb.toString(), HashMap.class);
//        Map<String, String[]> parameterMap = arg0.getParameterMap();
        String requestJson = sb.toString();
        String username = hashMap.get("username").toString();
        String passwords = hashMap.get("password").toString();
        String grantTypes = hashMap.get("grant_type").toString();
        String respJson = "";
        OAuth2AccessToken token;
        try {
            token = iAuthService.getToken(grantTypes, username, passwords, null);
            ResponseData success = success(token);
            JSONObject jsonObject = JSON.parseObject(JSONObject.toJSONString(success));
            JSONObject convert = JsonConvertUtil.Convert(jsonObject);
            respJson = convert.toJSONString();
        } catch (ResponseException e) {
            ResponseData fail;
            if (e.getStatus().equals(RespCodeEnum.USER_LOCKED.getMsgCode())) {
                fail = ResponseData.fail(RespCodeEnum.USER_LOCKED);
            } else if (e.getStatus().equals(RespCodeEnum.USER_OR_PASSWORD_ERROR.getMsgCode())) {
                fail = ResponseData.fail(RespCodeEnum.USER_OR_PASSWORD_ERROR);
            } else {
                fail = ResponseData.fail();
            }
            respJson = JSONObject.toJSONString(fail);
        }

        ServletOutputStream out = arg1.getOutputStream();
        out.write(respJson.getBytes());
        out.flush();
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {

    }
}


/**
 * 用户登录keycloak获取token
 */
public interface IAuthService {
    /**
     * 获取当前用户的accessToken
     *
     * @param grantType
     * @param username
     * @param password
     * @param refreshToken
     * @return
     */
    OAuth2AccessToken getToken(String grantType, String username, String password, String refreshToken) throws ResponseException;

}
/**
 * @Description: 用户登录keycloak获取token
 */
@Service
@Slf4j
@AllArgsConstructor
public class AuthServiceImpl implements IAuthService {

    private OAuth2ClientConfiguration oAuth2ClientConfiguration;
    ApplicationContext applicationContext;
    WorkPropertise workPropertise;
    @Resource
    private RestTemplate restTemplate;
    //用户名账号密码错误
    public static final String ERROR_USERNAME_PASSWORD = "Error requesting access token.";
    //用户被禁用
    public static final String USER_DISABLE = "Access token denied.";

    @Override
    public OAuth2AccessToken getToken(String grantType, String username, String password, String refreshToken) throws ResponseException {
        OAuth2AccessToken accessToken;
        ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
        resource.setUsername(username);
        resource.setPassword(password);
        oAuth2ClientConfiguration.configResource(resource);

        AccessTokenRequest accessTokenRequest = new DefaultAccessTokenRequest();
        OAuth2ClientContext oAuth2ClientContext = new DefaultOAuth2ClientContext(accessTokenRequest);
        OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource, oAuth2ClientContext);
        if (REFRESH_TOKEN.equals(grantType)) {
            ResourceOwnerPasswordAccessTokenProvider resourceOwnerPasswordAccessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
            accessToken = resourceOwnerPasswordAccessTokenProvider.refreshAccessToken(resource,
                    new DefaultOAuth2RefreshToken(refreshToken), accessTokenRequest);
        } else {
            try {
                SecurityContextHolder.getContext().setAuthentication(null);
                accessToken = oAuth2RestTemplate.getAccessToken();
            } catch (OAuth2AccessDeniedException e) {
                log.error("登录认证异常: ", e);
                // 判断异常信息,进行抛出
                if (ERROR_USERNAME_PASSWORD.equals(e.getMessage())) {
                    // 用户名密码错误
                    throw new ResponseException(RespCodeEnum.USER_OR_PASSWORD_ERROR);
                } else if (USER_DISABLE.equals(e.getMessage())) {
                    // 用户被禁用
                    throw new ResponseException(RespCodeEnum.USER_LOCKED);
                } else {
                    throw new ResponseException(RespCodeEnum.KC_USER_LOGIN_EXCEPTION);
                }
            }
        }
        return accessToken;
    }
}


//3.2 弃用keycloak内置的获取token的接口,自定义获取token接口

/**
 * @description: keycloak接口
 * @create: 2020-06-11 11:36
 **/
@Slf4j
@RestController
@RequestMapping("/keycloak")
public class AuthKeycloakController {

    @Autowired
    AuthKeycloakService keycloakService;
    @Autowired
    IAuthService iAuthService;

    @PostMapping("/oauth/token")
    public ResponseData getToken(@RequestBody @Validated TokenDTO tokenDTO) {
        OAuth2AccessToken token = iAuthService.getToken(tokenDTO.getGrant_type(), tokenDTO.getUsername(),
                tokenDTO.getPassword(), tokenDTO.getClient_id(), tokenDTO.getClient_secret(), tokenDTO.getRefresh_token());
        return ResponseData.success(token);
    }

}

public interface IAuthService {

    /**
     * 获取Client的token
     *
     * @param grantType
     * @param username
     * @param password
     * @param clientId
     * @param clientSecret
     * @param refreshToken
     * @return
     */
    OAuth2AccessToken getToken(String grantType, String username, String password, String clientId, String clientSecret, String refreshToken);

}

public class AuthServiceImpl implements IAuthService {

    private OAuth2ClientConfiguration oAuth2ClientConfiguration;
    ApplicationContext applicationContext;
    WorkPropertise workPropertise;
    @Resource
    private RestTemplate restTemplate;
    //用户名账号密码错误
    public static final String ERROR_USERNAME_PASSWORD = "Error requesting access token.";
    //用户被禁用
    public static final String USER_DISABLE = "Access token denied.";

    @Override
    public OAuth2AccessToken getToken(String grantType, String username, String password, String clientId, String clientSecret, String refreshToken) {

        BaseOAuth2ProtectedResourceDetails resource = null;
        switch (grantType) {
            case CLIENT_CREDENTIALS:
                resource = new ClientCredentialsResourceDetails();
                resource.setClientId(clientId);
                resource.setClientSecret(clientSecret);
                break;
            case PASSWORD:
                resource = new ResourceOwnerPasswordResourceDetails();
                ((ResourceOwnerPasswordResourceDetails) resource).setUsername(username);
                ((ResourceOwnerPasswordResourceDetails) resource).setPassword(password);
                resource.setClientId(clientId);
                resource.setClientSecret(clientSecret);
                break;
            case REFRESH_TOKEN:
                break;
            default:
                throw new ResponseException(RespCodeEnum.AUTHORIZATION_TYPE_NOT_SUPPORT);
        }

        resource.setAccessTokenUri(workPropertise.getTokenUrl());
        resource.setGrantType(grantType);
        resource.setClientId(workPropertise.getKcClientId());
        resource.setClientSecret(workPropertise.getKcClientSecret());
        resource.setScope(getScopesList("email", "profile"));

        OAuth2AccessToken accessToken;
        AccessTokenRequest accessTokenRequest = new DefaultAccessTokenRequest();
        OAuth2ClientContext oAuth2ClientContext = new DefaultOAuth2ClientContext(accessTokenRequest);
        OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource, oAuth2ClientContext);

        if (REFRESH_TOKEN.equals(grantType)) {
            ResourceOwnerPasswordAccessTokenProvider resourceOwnerPasswordAccessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
            accessToken = resourceOwnerPasswordAccessTokenProvider.refreshAccessToken(resource,
                    new DefaultOAuth2RefreshToken(refreshToken), accessTokenRequest);
        } else {
            SecurityContextHolder.getContext().setAuthentication(null);
            try {
                accessToken = oAuth2RestTemplate.getAccessToken();
            } catch (Exception e) {
                if (e instanceof OAuth2AccessDeniedException) {
                    // 账号密码错误 Access token denied.
                    throw new ResponseException(RespCodeEnum.PERMISSION_DENIED);
                }
                // 判断异常信息,进行抛出
                if (ERROR_USERNAME_PASSWORD.equals(e.getMessage())) {
                    // 用户名密码错误
                    throw new ResponseException(RespCodeEnum.USER_OR_PASSWORD_ERROR);
                } else if (USER_DISABLE.equals(e.getMessage())) {
                    // 用户被禁用
                    throw new ResponseException(RespCodeEnum.USER_LOCKED);
                } else {
                    throw new ResponseException(RespCodeEnum.KC_USER_LOGIN_EXCEPTION);
                }
            }
        }
        return accessToken;
    }

}

// 3.3 获取Master token
// 对于微服务间或不同平台间,系统对外接口的调用,希望既可以对接口做认证授权,又不希望通过接口的方式获取token,
// 我们可以直接通过配置文件拿到master域以超级管理员的方式获取token,此token拥有最高权限的token,仅限于内部调用 切勿暴露,代码实现:
   
    /**
     * 此token为Master realm admin-cli token 仅限于内部程序使用
     * 默认过期时间为60sec 若程序执行时间超过60sec 可以设置keycloak:
     * Master realm Admin-cli clients Advanced Settings Access Token Lifespan 10 min
     *
     * @return Master token
     */
    public String getMasterToken() {
        String token = null;
        //${keycloak.auth-server-url}
        String authServerUrl = workPropertise.getAuthServerUrl() + "realms/master/protocol/openid-connect/token";
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("client_id", workPropertise.getResource());
        map.add("username", workPropertise.getAdminUserName());
        map.add("password", workPropertise.getAdminPassword());
        map.add("grant_type", "password");
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, String>> multiValueMapHttpEntity = new HttpEntity<>(map, httpHeaders);
        try {
            ResponseEntity<String> responseEntity = restTemplate.exchange(authServerUrl, HttpMethod.POST, multiValueMapHttpEntity, String.class);
            JSONObject jsonObject = JSON.parseObject(responseEntity.getBody());
            // 获取值
            token = jsonObject.getString("access_token");
            log.debug("调用keycloak API获取Master token成功: {}", jsonObject);
        } catch (Exception e) {
            log.error("调用keycloak API获取Master token失败: ", e);
        }
        return "Bearer " + token;
    }


//4、keycloak用户、角色的增删改查,可以通过keycloak的java API实现,也可以通过keycloak提供的rest API实现
// 其实java API最后也是通过http的方式调用keycloak REST API实现的,个人感觉java API调用方便,rest API灵活

// 4.1、java API实现 这里仅以用户举例,角色、用户角色关系的调用类似
  /**
 * @Description: keyCloak客户端的初始化
 * @Author: csn
 * @Date: 2023/03/22 11:58
 */
@AllArgsConstructor
@Component
@Getter
@Setter
public class KeycloakAdminClient {

    @Resource
    private WorkPropertise workPropertise;
    private Keycloak keycloak;
    private RealmResource edge;
    private RealmResource master;

    /**
     * 初始化keycloak的方法
     */
    @PostConstruct
    private void init() {
        keycloak = Keycloak.getInstance(
                // keycloak 服务地址 "${keycloak.auth-server-url}"
                workPropertise.getAuthServerUrl(),
                // keycloak 管理员的域 "${kc.target-realm}"
                workPropertise.getTargetRealm(),
                //你自己创建的管理员的账号 "${kc.master-realm-user-name}"
                workPropertise.getAdminUserName(),
                // 你自己创建的管理员的密码 "${kc.master-realm-user-password}"
                workPropertise.getAdminPassword(),
                // admin-cli 客户端 ${keycloak.resource}
                workPropertise.getResource()
        );
        // 你创建的realm
        edge = keycloak.realm(workPropertise.getRealm());
        // master realm
        master = keycloak.realm(workPropertise.getTargetRealm());
    }

}


/**
 * @author csn
 * date 2021/5/7 15:22
 * description
 */
@Slf4j
@AllArgsConstructor
@Service
public class AuthKeycloakServiceImpl implements AuthKeycloakService {
    @Autowired
    private KeycloakAdminClient keycloakAdminClient;
    @Autowired
    private WorkPropertise workPropertise;

    @Override
    public void addKcUser(UserT userT) {
        RealmResource realmResource = keycloakAdminClient.getEdge();
        Keycloak keycloak = keycloakAdminClient.getKeycloak();

        UsersResource userResource = realmResource.users();
        UserRepresentation newUser = new UserRepresentation();
        // 构建用户信息用户——密码在创建完用户之后进行设置密码
        newUser.setUsername(userT.getUsername());
        newUser.setFirstName(userT.getName());
        // 创建用户
        Response createUserResponse = keycloak.realm(workPropertise.getRealm()).users().create(newUser);
        // 判断创建用户状态
        Response.StatusType createUserStatus = createUserResponse.getStatusInfo();
        // 返回请求的地址
        URI location = createUserResponse.getLocation();
        String userId;
        if (Response.Status.CREATED.getStatusCode() == createUserStatus.getStatusCode()) {
            log.info("keycloak client 创建用户成功,创建用户的URI:{}", location);
            // 获取用户id
            userId = createUserResponse.getLocation().getPath().replaceAll(".*/([^/]+)$", "$1");
            // 设置密码
            CredentialRepresentation passwordCred = new CredentialRepresentation();
            passwordCred.setTemporary(false);
            passwordCred.setType(CredentialRepresentation.PASSWORD);
            passwordCred.setValue(userT.getPassword());
            userResource.get(userId).resetPassword(passwordCred);
            log.info("keycloak client 同步创建{}用户成功", userT.getUsername());
            String salt = passwordCred.getSalt();
            String secretData = passwordCred.getSecretData();
        } else if (Response.Status.CONFLICT.getStatusCode() == createUserStatus.getStatusCode()) {
            log.info("keycloak client 同步获取{}用户成功", userT.getUsername());
        } else {
            log.error("新增{}用户异常", userT.getUsername());
        }
    }

    @Override
    public void updateKcUser(UserT userT) {
        Keycloak keycloak = keycloakAdminClient.getKeycloak();
        RealmResource realmResource = keycloakAdminClient.getEdge();
        UsersResource userResource = realmResource.users();

        // 获取keycloak指定用户
        List<UserRepresentation> search = keycloak.realm(workPropertise.getRealm()).users().search(userT.getUsername());
        if (search.size() == 1) {
            UserRepresentation representation = search.get(0);
            representation.setEnabled(EnabledEnum.getEnabledByCode(userT.getState()));
            UserRepresentation newUser = new UserRepresentation();
            newUser.setEnabled(EnabledEnum.getEnabledByCode(userT.getState()));
            newUser.setFirstName(userT.getName());
            userResource.get(representation.getId()).update(newUser);
        }
    }

    @Override
    public void resetPasswordKcUser(UserT userT, String newPassword) {
        try {
            RealmResource edge = keycloakAdminClient.getEdge();
            UsersResource users = edge.users();
            UserResource userResource = users.get(userT.getUsername());
            CredentialRepresentation credentialRepresentation = new CredentialRepresentation();
            credentialRepresentation.setTemporary(false);
            credentialRepresentation.setType(CredentialRepresentation.PASSWORD);
            credentialRepresentation.setValue(newPassword);
            // 重置用户密码
            userResource.resetPassword(credentialRepresentation);
        } catch (Exception e) {
            log.error("keyclaok客户端重置密码异常:", e);
            throw new ResponseException(RespCodeEnum.KC_USER_RESSET_EXCEPTION);
        }
    }
}


//4.2 REST API实现 这里以绑定用户-角色关系举例(虽然我们不使用keycloak的用户角色映射,但是想要使用的小伙伴可以参考)
// rest API 无非就是组装请求url,请求头,请求体,然后用restTemplate去发http请求
    public void syncUserRoleMapping(SysUserEntity sysUserEntity) {
        String masterToken = iAuthService.getMasterToken();
        Keycloak keycloak = keycloakAdminClient.getKeycloak();
        // 构建keycloak域角色映射url
        String url = workPropertise.getAuthServerUrl() + "admin/realms/" + workPropertise.getRealm() + "/users/" + sysUserEntity.getKeycloakId() + "/role-mappings/realm";
        // 获取用户角色名称列表
        List<String> roleNames = sysRoleMapper.selectUserRoleByUserId(sysUserEntity.getId());
        ArrayList<RoleRepresentation> roleMappingList = new ArrayList<>();
        for (String roleName : roleNames) {
            // 获取域角色by name
            List<RoleRepresentation> list = keycloak.realm(workPropertise.getRealm()).roles().list(roleName, true);
            roleMappingList.addAll(list);
        }
        String roleMappingJson = JSON.toJSONString(roleMappingList);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(HttpHeaders.AUTHORIZATION, masterToken);
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> request = new HttpEntity<>(roleMappingJson, httpHeaders);
        HttpHeaders getHeader = new HttpHeaders();
        getHeader.add(HttpHeaders.AUTHORIZATION, masterToken);
        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(getHeader);
        try {
            // *清除旧角色映射
            ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
            JSONArray jsonArray = JSONArray.parseArray(responseEntity.getBody());
            if (jsonArray != null) {
                List<RoleRepresentation> deleteRoles = jsonArray.toJavaList(RoleRepresentation.class);
                String deleteMapping = JSON.toJSONString(deleteRoles);
                HttpEntity<String> deleteRequest = new HttpEntity<>(deleteMapping, httpHeaders);
                restTemplate.exchange(url, HttpMethod.DELETE, deleteRequest, String.class);
            }
            //创建新角色映射
            ResponseEntity<String> addResponse = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
            log.debug("调用keycloak API成功,返回信息:{}", addResponse);
        } catch (Exception e) {
            log.error("调用keycloak API同步域角色映射失败,失败信息为:", e);
        }

    }

// 为什么要用用户-角色关系举例,因为我没有找到keycloak用户角色映射javaAPI,好像没有~ 所以用户角色映射暂时只好通过这种方式实现
// 希望找到相关javaAPI的小伙伴评论指出
// keycloak的rest API地址:https://www.keycloak.org/docs-api/20.0.3/rest-api/index.html


//5 对于已登录的用户 我们希望系统直接获取当前登录用户的用户名,可以通过以下方式实现,可以写在common包里 作为工具类给其他微服务使用
    /**
     * 获取当前登录用户名称
     *
     * @return username
     */
    public static String getUsername() {
        String username;
        try {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            username = authentication.getName();
        } catch (Exception e) {
            throw new UsernameNotFoundException("获取当前登录用户失败" + e);
        }
        return username;
    }

// 6 其他微服务引入keycloak 做接口认证授权 仅需两步
// 6.1、pom文件引入common的pom 6.2、yml配置文件加上以下keycloak配置
keycloak:
  auth-server-url: http://127.0.0.1:8411/auth/
  resource: admin-cli
  realm: test
  principal-attribute: preferred_username
  use-resource-role-mappings: false
  ssl-required: none

// 6.3、对应的微服务加以下配置
@KeycloakConfiguration
@RequiredArgsConstructor
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
    // 接口放开
    String[] PERMIT_URL = {"/user/info","/user/login/info","/xxx"};

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
        grantedAuthorityMapper.setPrefix("ROLE_");
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public org.keycloak.adapters.KeycloakConfigResolver keycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http
                .cors()
                .and()
                .csrf()
                .disable()
                .authorizeRequests()
//                .antMatchers()
                // 接口放开
                .antMatchers(PERMIT_URL)
                .permitAll()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                // 异常处理
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
                .and()
                .httpBasic();

    }

}


参考 keycloak授权服务指南:
https://www.keycloak.org/docs/latest/authorization_services/

附:keycloak客户端配置文件:test.json