keycloak + spring security 토큰 발급 연대기

뿌이·2022년 3월 21일
0

스프링 클라우드

목록 보기
29/32

나는 access token을 keycloak API 공식 문서에 있는 방법대로 얻어오는 것을 선택했다.
하지만 대부분의 블로그 글은 spring security랑 연동을 햇다.
그때는 내가 지식이 부족해서 왜 저렇게 하지.. 생각했는데
다 이유가 있었다.

이유인 즉슨, access token을 직접 나처럼 decode 해서 원하는정보를 json 파싱하는 방법은
별로인게.. 일단, MSA 기반에서는 각 서비스가 각각의 서버를 갖고있기 때문에
서버가 다르면 model도 session도 무용지물이다.
그래서 spring-httpSession에서 redis를 사용하는게 많았다.

근데 로그아웃 기능을 구현한다면 redis를 사용해야하지만
로그아웃 기능 빼고 로그인 기능만, redis 없이 토큰 이동이 가능한 경우는

바로 바로~ spring security랑 keycloak이랑 이어서, principal 객체에 토큰정보를 저장시키고,
keycloak Accesstoken을 jwtToken으로 형변환 시켜서
token 안에 있는 userinfo를 getUserAttributes()로 받는것이다.

한번 해보겟음!!!!

gateway에 먼저 keycloak연결하기

SecurityConfig.java

package com.dream.gatewayservice.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@Configuration
public class SecurityConfig {
	
	@Bean
	public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
		http.authorizeExchange().pathMatchers("/menu/**").permitAll()
			.and().authorizeExchange().anyExchange().authenticated()
			.and().oauth2Login() // to redirect to oauth2 login page.
			.and().csrf().disable();
		return http.build();
	}
}

이렇게 일단 시큐리티 설정해두고

application.yml

  security:
    oauth2:
      client:
        provider:
          keycloak:
            issuer-uri: http://너님의 키클락사용할 pc url:8080/auth/realms/MSA
        registration:
          keycloak:
#            provider: keycloak
            client-id: spring-gateway-client
            client-secret: 클라이언트 비번
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/login/oauth2/code/keycloak'      
            
keycloak:
  realm: MSA
#  public-client: false
  resource: spring-gateway-client
  auth-server-url: http://너님의 키클락사용할 pc url:8080/auth

이런것 추가해준다.
keycloak정보를 알아야하니깐~!

build.gradle

plugins {
	id 'org.springframework.boot' version '2.6.3'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.dream'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

ext {
	set('springCloudVersion', "2021.0.1")
	set('keyCloakVersion', "16.1.1") //추가
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' //추가
	implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
	//implementation 'org.keycloak:keycloak-spring-boot-starter'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
		mavenBom "org.keycloak.bom:keycloak-adapter-bom:${keyCloakVersion}" //추가
	}
}

tasks.named('test') {
	useJUnitPlatform()
}

//추가 라고 써놓은 부분들을 모두 추가해준다. 그러면 keycloak이 연동이 된것임

MS에 keycloak과 security 연결하여 token 받기

ManageConfig.java

package com.dream.manage.config;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class ManageConfig extends WebSecurityConfigurerAdapter {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests().anyRequest().authenticated()
				.and().oauth2ResourceServer()
                .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
				.and().anonymous().disable();
	}

	private Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter() {
		JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
		jwtConverter.setJwtGrantedAuthoritiesConverter(new RealmRoleConverter());
		return jwtConverter;
	}

	public class RealmRoleConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
		@Override
		public Collection<GrantedAuthority> convert(Jwt jwt) {
			final Map<String, List<String>> realmAccess = (Map<String, List<String>>) jwt.getClaims()
					.get("realm_access");
			return realmAccess.get("roles").stream().map(roleName -> "ROLE_" + roleName) 
            // prefix required by Spring
																							
           // Security for roles.
					.map(SimpleGrantedAuthority::new).collect(Collectors.toList());
		}
	}
}

먼저 config가 각 서비스별로 있어야한다.

application.yml

  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://keycloak 돌릴 pc url:8080/auth/realms/MSA/protocol/openid-connect/certs
#      client:
#        provider:
#          keycloak:
#            issuer-uri: http://keycloak 돌릴 pc url:8080/auth/realms/MSA
#        registration:
#          keycloak:
##            provider: keycloak
#            client-id: spring-gateway-client
#            client-secret: keycloak client 비번
#            authorization-grant-type: authorization_code
#            redirect-uri: '{baseUrl}/login/oauth2/code/keycloak' 
keycloak:
  realm: MSA
  bearer-only: true
  ssl-required: external
  resource: spring-gateway-client
  auth-server-url: http://keycloak 돌릴 pc url:8080/auth
  credentials:
    secret: keycloak client 비번

이거 넣어주고

build.gradle

plugins {
	id 'org.springframework.boot' version '2.6.4'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.dream'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

ext {
	set('springCloudVersion', "2021.0.1")
	set('keycloakversion', "16.1.1") //추가
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-jdbc'
	implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' //추가
	implementation 'org.springframework.boot:spring-boot-starter-security' //추가
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.keycloak:keycloak-spring-boot-starter' //추가
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
	implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'mysql:mysql-connector-java'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
		mavenBom "org.keycloak.bom:keycloak-adapter-bom:${keycloakversion}" //추가
	}
}

tasks.named('test') {
	useJUnitPlatform()
}

//추가 라고 한 부분들 모두 추가하삼
그다음 controller 에서 principal 객체에서 token 뽑아내고 token에서 필요한 정보를 view로 넘기는거
해보겠삼~

manageController.java 수정필요

package com.dream.manage.controller;

import java.security.Principal;

import javax.annotation.security.RolesAllowed;

import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import com.dream.manage.dto.ManageDto;
import com.dream.manage.service.ManageService;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Controller
@RequiredArgsConstructor
@Slf4j
public class ManageController {
	private ManageService service;

	@RolesAllowed({ "ADMIN" }) //realm 의 role에 따라 mapping
	@GetMapping("/register")
	public String register(Principal principal, Model model) {
		JwtAuthenticationToken token = (JwtAuthenticationToken) principal; //jwt로 형변환
		log.info("toString : "+token.getTokenAttributes().toString());
		model.addAttribute("list", token.getTokenAttributes());
		return "register";
	}

	@RolesAllowed({ "ADMIN" })
	@PostMapping("/register")
	public String register(@ModelAttribute("dto") ManageDto dto) {
		log.info("컨트롤러 시작" + dto.getDescription());
		service.register(dto);
		log.info("컨트롤러 끝");
		return "/regist_success";
	}

	@PostMapping("/modify")
	public String modify() {
		return "asd";
	}

	@GetMapping("/delete")
	public String delete() {
		return "asd";
	}
}

register.html에서는

<div th:text="${list.preferred_username}"> 

으로 꺼내서 쓰면 된다.

이렇게 다른 MS도 하면된다.

결과


먼저 localhost:8000/manage/register에 user로 접근해보겠다.
port번호 8000번은 api gateway 포트번호이다.

@RolesAllowed({ "ADMIN" }) 에 의해 거부되었음

이번엔 admin으로 접근해보겠음


이렇게 user name이 나온것을 알수있다.

profile
기록이 쌓이면 지식이 된다.

0개의 댓글