메인 콘텐츠로 건너뛰기

Casdoor를 위한 OIDC 통합을 가진 Spring Security 필터

Casdoor는 OIDC 및 다양한 다른 프로토콜을 지원하는 오픈 소스 IDP입니다. 이 글에서는 Spring Security 필터와 OIDC를 사용하여 애플리케이션과 Casdoor를 통합하는 방법을 살펴봅니다.

1단계: Casdoor 배포

먼저, Casdoor 서버를 배포해야 합니다. 서버 설치 지침에 대해서는 공식 문서를 참조하십시오. 성공적인 배포 후, 확인하십시오:

  • Casdoor 서버가 http://localhost:8000에서 실행 중입니다.
  • Casdoor 로그인 페이지를 http://localhost:7001에서 볼 수 있습니다.
  • admin123의 자격 증명으로 로그인하여 로그인 기능을 테스트할 수 있습니다.

이 단계들을 확인한 후, 아래 단계를 따라 애플리케이션과 Casdoor를 통합하십시오.

2단계: Casdoor 애플리케이션 구성

  • 새로운 Casdoor 애플리케이션을 만들거나 기존의 것을 사용하십시오.
  • 리디렉션 URL을 추가하십시오. 다음 섹션에서 리디렉션 URL을 얻는 방법에 대한 자세한 정보를 찾을 수 있습니다. Casdoor 애플리케이션 설정
  • 인증서 편집 페이지에서 Certificate를 얻으십시오. Casdoor 인증 설정
  • 필요에 따라 제공자 및 기타 설정을 추가하십시오.

Application Name, Organization Name, Redirect URL, Client ID, Client Secret, 그리고 Certificate의 값을 애플리케이션 설정 페이지에서 얻을 수 있습니다. 다음 단계에서 이들을 사용할 것입니다.

단계 3: Spring Security 구성

토큰을 처리하기 위해 Spring Security 필터의 설정을 사용자 정의할 수 있습니다:

주의

특히 <Client ID> 등과 같은 설정 값을 반드시 자신의 Casdoor 인스턴스로 교체해야 합니다.

server:
port: 8080
casdoor:
endpoint: http://CASDOOR_HOSTNAME:8000
client-id: <Client ID>
client-secret: <Client Secret>
certificate: <Certificate>
organization-name: <Organization Name>
application-name: <Application Name>
redirect-url: http://FRONTEND_HOSTNAME/callback
주의

프론트엔드 애플리케이션의 경우, <FRONTEND_HOSTNAME>의 기본값은 localhost:3000입니다. 이 데모에서 리디렉트 URL은 http://localhost:3000/callback입니다. 이를 꼭 casdoor 애플리케이션에서 구성해야 합니다.

단계 4: 프론트엔드 구성

casdoor-js-sdk를 설치하고 다음과 같이 SDK를 구성해야 합니다:

  1. casdoor-js-sdk를 설치하십시오.

    npm i casdoor-js-sdk 
    # or
    yarn add casdoor-js-sdk
  2. SDK를 설정하십시오.

    import Sdk from "casdoor-js-sdk";

    // Serverurl is the URL where spring security is deployed
    export const ServerUrl = "http://BACKEND_HOSTNAME:8080";

    const sdkConfig = {
    serverUrl: "http://CASDOOR_HOSTNAME:8000",
    clientId: "<your client id>",
    appName: "<your application name>",
    organizationName: "<your organization name>",
    redirectPath: "/callback",
    };

    export const CasdoorSDK = new Sdk(sdkConfig);

단계 5: 데모 설정

  1. Spring Boot 애플리케이션을 생성합니다.

  2. JWT를 처리하기 위한 몇 가지 설정을 추가합니다.

    @EnableWebSecurity
    public class SecurityConfig {

    private final JwtTokenFilter jwtTokenFilter;

    public SecurityConfig(JwtTokenFilter jwtTokenFilter) {
    this.jwtTokenFilter = jwtTokenFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    // enable CORS and disable CSRF
    http = http.cors(corsConfig -> corsConfig
    .configurationSource(configurationSource())
    ).csrf().disable();

    // set session management to stateless
    http = http
    .sessionManagement()
    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    .and();

    // set permissions on endpoints
    http.authorizeHttpRequests(authorize -> authorize
    .mvcMatchers("/api/redirect-url", "/api/signin").permitAll()
    .mvcMatchers("/api/**").authenticated()
    );

    // set unauthorized requests exception handler
    http = http
    .exceptionHandling()
    .authenticationEntryPoint(
    (request, response, ex) -> ResponseUtils.fail(response, "unauthorized")
    )
    .and();

    // add JWT token filter
    http.addFilterBefore(
    jwtTokenFilter,
    UsernamePasswordAuthenticationFilter.class
    );
    return http.build();
    }

    // ...

    }
  3. 토큰 검증이 필요한 요청을 가로채기 위해 간단한 JWT 필터를 추가합니다.

    @Component
    public class JwtTokenFilter extends OncePerRequestFilter {

    private final CasdoorAuthService casdoorAuthService;

    public JwtTokenFilter(CasdoorAuthService casdoorAuthService) {
    this.casdoorAuthService = casdoorAuthService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
    HttpServletResponse response,
    FilterChain chain)
    throws ServletException, IOException {
    // get authorization header and validate
    final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
    if (!StringUtils.hasText(header) || !header.startsWith("Bearer ")) {
    chain.doFilter(request, response);
    return;
    }

    // get jwt token and validate
    final String token = header.split(" ")[1].trim();

    // get user identity and set it on the spring security context
    UserDetails userDetails = null;
    try {
    CasdoorUser casdoorUser = casdoorAuthService.parseJwtToken(token);
    userDetails = new CustomUserDetails(casdoorUser);
    } catch (CasdoorAuthException exception) {
    logger.error("casdoor auth exception", exception);
    chain.doFilter(request, response);
    return;
    }

    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
    userDetails,
    null,
    AuthorityUtils.createAuthorityList("ROLE_casdoor")
    );

    authentication.setDetails(
    new WebAuthenticationDetailsSource().buildDetails(request)
    );

    SecurityContextHolder.getContext().setAuthentication(authentication);
    chain.doFilter(request, response);
    }

    }

    사용자가 인증이 필요한 인터페이스에 접근하면, JwtTokenFilter는 요청 헤더의 Authorization에서 토큰을 얻고 검증합니다.

  4. 사용자가 Casdoor에 로그인할 때 처리할 Controller를 정의합니다. 사용자가 로그인한 후, 서버로 리디렉트되며 codestate를 가지게 됩니다. 그런 다음 서버는 이 두 매개변수를 통해 Casdoor에서 사용자의 신원을 검증하고 token을 얻어야 합니다.

    @RestController
    public class UserController {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    private final CasdoorAuthService casdoorAuthService;

    // ...

    @PostMapping("/api/signin")
    public Result signin(@RequestParam("code") String code, @RequestParam("state") String state) {
    try {
    String token = casdoorAuthService.getOAuthToken(code, state);
    return Result.success(token);
    } catch (CasdoorAuthException exception) {
    logger.error("casdoor auth exception", exception);
    return Result.failure(exception.getMessage());
    }
    }

    // ...
    }

단계 6: 데모 시도

브라우저를 통해 프론트엔드 애플리케이션에 접근할 수 있습니다. 로그인하지 않은 경우 로그인 버튼이 표시됩니다. 그것을 클릭하면 Casdoor 로그인 페이지로 리디렉트됩니다.

루트 페이지를 방문하면,환영합니다

Casdoor Login 버튼을 클릭하면, 페이지가 Casdoor의 로그인 페이지로 리디렉트됩니다. casdoor

로그인한 후에는 /로 리디렉트됩니다. 리소스