it-source

Spring Security OAuth2와 Spring Social 통합

criticalcode 2023. 2. 18. 20:13
반응형

Spring Security OAuth2와 Spring Social 통합

저는 Spring Boot + Spring Security OAuth2 어플리케이션을 사용하고 있는데, Dave Syer의 예에서 영감을 얻은 것 같습니다.응용 프로그램은 리소스 소유자 암호 자격 증명 플로우를 사용하는 단일 공용 클라이언트가 있는 OAuth2 인증 서버로 구성되어 있습니다.성공한 토큰은 JWT로 설정됩니다.

퍼블릭 Angular 클라이언트는 클라이언트 ID와 시크릿을 포함한 기본 인증 헤더를 사용하여 /oauth/token에 POST 요청을 전송합니다(비밀이 비공개가 아닌 경우에도 이것은 클라이언트를 인증하는 가장 쉬운 방법입니다).요청 본문에는 "password"의 사용자 이름, 비밀번호 및 허가 유형이 포함됩니다.

인증 서버일 뿐만 아니라 사용자, 팀 및 조직을 위한 RESTful 리소스 서버입니다.

Spring Social을 사용하여 SSO 인증 플로우를 추가하려고 합니다./auth/[provider]를 통해 외부 공급자를 통해 인증하도록 Spring Social을 설정했지만 다음 요청에서는 Security Context가 올바르게 설정되지 않습니다.Spring Security OAuth 서버 또는 클라이언트가 Security Context를 덮어쓰고 있을 수 있습니다.

Spring Social 플로우 후에 SecurityContext를 올바르게 설정할 수 있는 경우 사전 인증된 사용자의 SecurityContextHolder를 체크하는 "social"의 새로운 인가 유형을 사용할 수 있는 새로운 TokenGranter가 있습니다.

SecurityContext(Spring OAuth+Social 통합의 문제인 것 같다)에 대한 특정 문제에 대한 해결책과 외부 프로바이더와의 인증 및 자체 인증 서버에서 유효한 JWT를 취득하는 다른 접근 방식 모두에 관심이 있습니다.

감사합니다!

JHIPster가 만든 웹 어플리케이션에서도 비슷한 문제가 있었습니다.마침내 나는 그 일을 하기로 결정했다.SocialAuthenticationFilterSocial ( Social )SpringSocialConfigurer소셜 로그인에 성공하면 서버는 자동으로 "소유" 액세스 토큰을 생성하여 클라이언트 앱으로 리다이렉트하여 반환합니다.

제 시도는 이렇습니다.

@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter implements EnvironmentAware {

    //...

    @Inject
    private AuthorizationServerTokenServices authTokenServices;

    @Override
    public void configure(HttpSecurity http) throws Exception {

        SpringSocialConfigurer socialCfg = new SpringSocialConfigurer();
        socialCfg
            .addObjectPostProcessor(new ObjectPostProcessor<SocialAuthenticationFilter>() {
                @SuppressWarnings("unchecked")
                public SocialAuthenticationFilter postProcess(SocialAuthenticationFilter filter){
                    filter.setAuthenticationSuccessHandler(
                            new SocialAuthenticationSuccessHandler(
                                    authTokenServices,
                                    YOUR_APP_CLIENT_ID
                            )
                        );
                    return filter;
                }
            });

        http
            //... lots of other configuration ...
            .apply(socialCfg);
    }        
}

★★★★★★★★★★★★★★★★.SocialAuthenticationSuccessHandler 링크:

public class SocialAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    public static final String REDIRECT_PATH_BASE = "/#/login";
    public static final String FIELD_TOKEN = "access_token";
    public static final String FIELD_EXPIRATION_SECS = "expires_in";

    private final Logger log = LoggerFactory.getLogger(getClass());
    private final AuthorizationServerTokenServices authTokenServices;
    private final String localClientId;

    public SocialAuthenticationSuccessHandler(AuthorizationServerTokenServices authTokenServices, String localClientId){
        this.authTokenServices = authTokenServices;
        this.localClientId = localClientId;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
            HttpServletResponse response, Authentication authentication)
                    throws IOException, ServletException {
        log.debug("Social user authenticated: " + authentication.getPrincipal() + ", generating and sending local auth");
        OAuth2AccessToken oauth2Token = authTokenServices.createAccessToken(convertAuthentication(authentication)); //Automatically checks validity
        String redirectUrl = new StringBuilder(REDIRECT_PATH_BASE)
            .append("?").append(FIELD_TOKEN).append("=")
            .append(encode(oauth2Token.getValue()))
            .append("&").append(FIELD_EXPIRATION_SECS).append("=")
            .append(oauth2Token.getExpiresIn())
            .toString();
        log.debug("Sending redirection to " + redirectUrl);
        response.sendRedirect(redirectUrl);
    }

    private OAuth2Authentication convertAuthentication(Authentication authentication) {
        OAuth2Request request = new OAuth2Request(null, localClientId, null, true, null,
                null, null, null, null);
        return new OAuth2Authentication(request,
                //Other option: new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), "N/A", authorities)
                new PreAuthenticatedAuthenticationToken(authentication.getPrincipal(), "N/A")
                );
    }

    private String encode(String in){
        String res = in;
        try {
            res = UriUtils.encode(in, GeneralConstants.ENCODING_UTF8);
        } catch(UnsupportedEncodingException e){
            log.error("ERROR: unsupported encoding: " + GeneralConstants.ENCODING_UTF8, e);
        }
        return res;
    }
}

을 웹 앱으로 합니다./#/login?access_token=my_access_token&expires_in=seconds_to_expiration하는 「」를 하고 있는 REDIRECT_PATH_BASESocialAuthenticationSuccessHandler.

도움이 됐으면 좋겠어요.

우선, 이러한 사용 사례에 대해서는 패스워드 부여에서 벗어날 것을 강력히 권장합니다.
퍼블릭 클라이언트(JavaScript, 설치된 애플리케이션)는 클라이언트 기밀을 유지할 수 없습니다.따라서 클라이언트 기밀은 할당되어서는 안 됩니다.JavaScript 코드를 검사하는 방문자는 누구나 기밀을 검출할 수 있기 때문에 프로세스 중에 가지고 있는 것과 동일한 인증 페이지를 구현할 수 있습니다.

암묵적 허가는 정확히 당신이 하고 있는 일에 대해 작성되었습니다.
리다이렉션 기반 플로우를 사용하면 인증 메커니즘을 각 애플리케이션에 할당하는 것이 아니라 인증 서버에 맡길 수 있는 장점이 있습니다.이는 대부분 SSO(Single Sign On)의 정의입니다.

그래서 당신의 질문은 제가 방금 답변한 것과 밀접하게 관련되어 있습니다.스프링 OAuth2 서버와 서드파티 OAuth 프로바이더입니다.

답을 요약하면:

마지막으로 인가 서버가 AuthorizationEndpoint : /oauth/authorize를 보호하는 방법에 대해 설명합니다.인가 서버가 동작하기 때문에 Web Security Configurer Adapter를 확장하는 설정 클래스가 이미 있습니다.이 클래스는 formLogin을 사용하여 /oauth/authorize의 보안을 처리합니다.거기서 사회적 요소를 통합해야 합니다.

달성하려는 항목에 암호 부여를 사용할 수 없습니다. 공용 클라이언트가 권한 부여 서버로 리디렉션하도록 해야 합니다.으로 인가 합니다.으로 리다이렉트 됩니다./oauth/authorize엔드 포인트

위의 좋은 답변(https://stackoverflow.com/a/33963286/3351474)부터 시작했는데 Spring Security 버전(4.2.8)부터입니다.RELEASE) 실패.그 이유는org.springframework.security.access.intercept.AbstractSecurityInterceptor#authenticateIfRequiredPreAuthenticatedAuthenticationToken인증되지 않았습니다. 됨(Granted) .당국은 통과되어야 한다.URL 파라미터로 토큰을 공유하는 것은 좋지 않을 뿐만 아니라 항상 HTTP 페이로드 또는 헤더로 숨겨야 합니다. 에 삽입됩니다.${token}플레이스 홀더 필드

개정판은 다음과 같습니다.

주의: 사용하시는 제품UserDetails구현 중입니다.org.springframework.security.core.userdetails.UserDetails

@Component
public class SocialAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private OAuth2TokenStore tokenStore;

    @Qualifier("tokenServices")
    @Autowired
    private AuthorizationServerTokenServices authTokenServices;

    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
        IClient user = ((SocialUserDetails) authentication.getPrincipal()).getUser();
        // registration is not finished, forward the user, a marker interface 
        // IRegistration is used here, remove this if there no two step approach to 
        // create a user from a social network
        if (user instanceof IRegistration) {
            response.sendRedirect(subscriberRegistrationUrl + "/" + user.getId());
        }
        OAuth2AccessToken token = loginUser(user);
        // load a HTML template from the class path and replace the token placeholder within, the HTML should contain a redirect to the actual page, but must store the token in a safe place, e.g. for preventing CSRF in the `sessionStorage` JavaScript storage.
        String html = IOUtils.toString(getClass().getResourceAsStream("/html/socialLoginRedirect.html"));
        html = html.replace("${token}", token.getValue());
        response.getOutputStream().write(html.getBytes(StandardCharsets.UTF_8));
    }

    private OAuth2Authentication convertAuthentication(Authentication authentication) {
        OAuth2Request request = new OAuth2Request(null, authentication.getName(),
                authentication.getAuthorities(), true, null,
                null, null, null, null);
        // note here the passing of the authentication.getAuthorities()
        return new OAuth2Authentication(request,
                new PreAuthenticatedAuthenticationToken(authentication.getPrincipal(), "N/A",  authentication.getAuthorities())
        );
    }

    /**
     * Logs in a user.
     */
    public OAuth2AccessToken loginUser(IClient user) {
        SecurityContext securityContext = SecurityContextHolder.getContext();
        UserDetails userDetails = new UserDetails(user);
        Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, "N/A", userDetails.getAuthorities());
        securityContext.setAuthentication(authentication);
        OAuth2Authentication oAuth2Authentication = convertAuthentication(authentication);
        // delete the token because the client id in the DB is calculated as hash of the username and client id (here also also identical to username), this would be identical to the
        // to an existing user. This existing one can come from a user registration or a previous user with the same name.
        // If a new entity with a different ID is used the stored token hash would differ and the the wrong token would be retrieved 
        tokenStore.deleteTokensForUserId(user.getUsername());
        OAuth2AccessToken oAuth2AccessToken = authTokenServices.createAccessToken(oAuth2Authentication);
        // the DB id of the created user is returned as additional data, can be 
        // removed if not needed
        ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(new HashMap<>());
        oAuth2AccessToken.getAdditionalInformation().put("userId", user.getId());
        return oAuth2AccessToken;
    }

}

socialLoginRedirect.html:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Example App</title>
    <meta http-equiv="Refresh" content="0; url=/index.html#/home"/>
</head>
<script>
     window.sessionStorage.setItem('access_token', '${token}');
</script>
<body>
<p>Please follow <a href="/index.html#/home">this link</a>.</p>
</body>
</html>

의 컨피규격WebSecurityConfigurerAdapter:

@Configuration
@EnableWebSecurity
@EnableWebMvc
@Import(WebServiceConfig.class)
public class AuthenticationConfig extends WebSecurityConfigurerAdapter {

    @Value("${registrationUrl}")
    private String registrationUrl;

    @Autowired
    private SocialAuthenticationSuccessHandler socialAuthenticationSuccessHandler;

    @Value("${loginUrl}")
    private String loginUrl;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        List<String> permitAllUrls = new ArrayList<>();
        // permit social log in
        permitAllUrls.add("/auth/**");
        http.authorizeRequests().antMatchers(permitAllUrls.toArray(new String[0])).permitAll();

        SpringSocialConfigurer springSocialConfigurer = new SpringSocialConfigurer();
        springSocialConfigurer.signupUrl(registrationUrl);
        springSocialConfigurer.postFailureUrl(loginUrl);
        springSocialConfigurer
                .addObjectPostProcessor(new ObjectPostProcessor<SocialAuthenticationFilter>() {
                    @SuppressWarnings("unchecked")
                    public SocialAuthenticationFilter postProcess(SocialAuthenticationFilter filter){
                        filter.setAuthenticationSuccessHandler(socialAuthenticationSuccessHandler);
                        return filter;
                    }
                });
        http.apply(springSocialConfigurer);

        http.logout().disable().csrf().disable();

        http.requiresChannel().anyRequest().requiresSecure();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

spring oauth2를 구현하여 rest 서비스를 보호하고 소셜 로그인과 암묵적인 사인업을 처음 로그인 시 추가로 추가하였습니다.사용자의 경우 소셜 사용자의 토큰 생성 문제만으로 사용자 이름과 패스워드를 사용하여 토큰을 생성할 수 있습니다.이를 위해 /oauth/token 요구를 대행 수신하는 필터를 구현해야 합니다.processing. 여기서 소셜 사용자용 토큰을 생성하고 사용자 이름과 페이스북 토큰을 전달하고 싶다면 여기서 페이스북 토큰을 비밀번호로 사용할 수 있습니다.페이스북 토큰이 업데이트되면 사용자 테이블에서 토큰을 업데이트하기 위해 DB 트리거도 작성해야 합니다. ...이것이 도움이 될 수 있습니다.

언급URL : https://stackoverflow.com/questions/32313821/integrate-spring-security-oauth2-and-spring-social

반응형