[java] Spring을 통한 RESTful 인증

문제:
민감한 정보가 포함 된 Spring MVC 기반 RESTful API가 있습니다. API는 보안되어야하지만 각 요청과 함께 사용자의 자격 증명 (사용자 / 패스 콤보)을 보내는 것은 바람직하지 않습니다. REST 지침 및 내부 비즈니스 요구 사항에 따라 서버는 상태 비 저장 상태로 유지되어야합니다. API는 매시업 스타일의 접근 방식으로 다른 서버에서 사용합니다.

요구 사항 :

  • 클라이언트는 .../authenticate자격 증명을 사용 하여 (보호되지 않은 URL)을 요청합니다 . 서버는 서버가 향후 요청을 확인하고 상태 비 저장 상태를 유지하기에 충분한 정보가 포함 된 보안 토큰을 반환합니다. 이것은 Spring Security의 Remember-Me Token 과 동일한 정보로 구성 될 수 있습니다 .

  • 클라이언트는 이전에 얻은 토큰을 쿼리 매개 변수 (또는 덜 바람직하게는 HTTP 요청 헤더)로 추가하여 다양한 (보호 된) URL에 후속 요청을합니다.

  • 클라이언트는 쿠키를 저장할 것으로 예상 할 수 없습니다.

  • Spring을 이미 사용하고 있으므로 솔루션은 Spring Security를 ​​사용해야합니다.

우리는이 일을하려고 노력하면서 벽에 머리를 대고 있었기 때문에 누군가가 이미이 문제를 해결했을 것입니다.

위의 시나리오에서 이러한 특정 요구를 어떻게 해결할 수 있습니까?



답변

우리는 OP에 설명 된대로 정확하게 작동하도록 노력했으며 다른 누군가가 솔루션을 사용할 수 있기를 바랍니다. 우리가 한 일은 다음과 같습니다.

다음과 같이 보안 컨텍스트를 설정하십시오.

<security:http realm="Protected API" use-expressions="true" auto-config="false" create-session="stateless" entry-point-ref="CustomAuthenticationEntryPoint">
    <security:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
    <security:intercept-url pattern="/authenticate" access="permitAll"/>
    <security:intercept-url pattern="/**" access="isAuthenticated()" />
</security:http>

<bean id="CustomAuthenticationEntryPoint"
    class="com.demo.api.support.spring.CustomAuthenticationEntryPoint" />

<bean id="authenticationTokenProcessingFilter"
    class="com.demo.api.support.spring.AuthenticationTokenProcessingFilter" >
    <constructor-arg ref="authenticationManager" />
</bean>

보시다시피, 우리는 custom을 만들었습니다. 이 필터는 AuthenticationEntryPoint기본적으로 401 Unauthorized요청이 필터 체인에서 인증되지 않은 경우를 반환합니다 AuthenticationTokenProcessingFilter.

CustomAuthenticationEntryPoint :

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Authentication token was either missing or invalid." );
    }
}

AuthenticationTokenProcessingFilter :

public class AuthenticationTokenProcessingFilter extends GenericFilterBean {

    @Autowired UserService userService;
    @Autowired TokenUtils tokenUtils;
    AuthenticationManager authManager;

    public AuthenticationTokenProcessingFilter(AuthenticationManager authManager) {
        this.authManager = authManager;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        @SuppressWarnings("unchecked")
        Map<String, String[]> parms = request.getParameterMap();

        if(parms.containsKey("token")) {
            String token = parms.get("token")[0]; // grab the first "token" parameter

            // validate the token
            if (tokenUtils.validate(token)) {
                // determine the user based on the (already validated) token
                UserDetails userDetails = tokenUtils.getUserFromToken(token);
                // build an Authentication object with the user's info
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
                // set the authentication into the SecurityContext
                SecurityContextHolder.getContext().setAuthentication(authManager.authenticate(authentication));
            }
        }
        // continue thru the filter chain
        chain.doFilter(request, response);
    }
}

분명히, TokenUtils몇 가지 특유의 (대부분의 경우에만) 코드를 포함하며 쉽게 공유 할 수 없습니다. 인터페이스는 다음과 같습니다.

public interface TokenUtils {
    String getToken(UserDetails userDetails);
    String getToken(UserDetails userDetails, Long expiration);
    boolean validate(String token);
    UserDetails getUserFromToken(String token);
}

좋은 출발을해야합니다. 행복한 코딩. 🙂


답변

다이제스트 액세스 인증을 고려할 수 있습니다 . 기본적으로 프로토콜은 다음과 같습니다.

  1. 클라이언트로부터 요청
  2. 서버가 고유 한 nonce 문자열로 응답
  3. 클라이언트는 nonce로 해시 된 사용자 이름 및 비밀번호 (및 기타 값) md5를 제공합니다. 이 해시는 HA1이라고합니다
  4. 그런 다음 서버는 고객의 신원을 확인하고 요청 된 자료를 제공 할 수 있습니다
  5. 서버가 새로운 nonce를 제공 ​​할 때까지 nonce와의 통신을 계속할 수 있습니다 (카운터는 재생 공격을 제거하는 데 사용됨)

이 모든 통신은 jmort253이 지적한 것처럼 일반적으로 URL 매개 변수로 민감한 자료를 통신하는 것보다 더 안전한 헤더를 통해 이루어집니다.

다이제스트 액세스 인증은 Spring Security에서 지원합니다 . 문서에서 클라이언트의 일반 텍스트 비밀번호에 액세스해야한다고 말하지만 클라이언트에 대한 HA1 해시가 있으면 성공적으로 인증 할 수 있습니다 .


답변

정보를 전달하는 토큰과 관련하여 JSON 웹 토큰 ( http://jwt.io )은 훌륭한 기술입니다. 주요 개념은 토큰에 정보 요소 (클레임)를 포함시킨 다음 전체 토큰에 서명하여 유효성 검사 측에서 클레임이 실제로 신뢰할 수 있는지 확인할 수 있도록하는 것입니다.

이 Java 구현을 사용합니다 : https://bitbucket.org/b_c/jose4j/wiki/Home

Spring 모듈 (spring-security-jwt)도 있지만, 그것이 지원하는 것을 조사하지 않았습니다.


답변

JSON WebToken과 함께 OAuth를 사용하지 않는 이유

http://projects.spring.io/spring-security-oauth/

OAuth2는 표준화 된 인증 프로토콜 / 프레임 워크입니다. 공식 OAuth2 사양에 따라 :

자세한 정보는 여기에서 찾을 수 있습니다


답변