문제:
민감한 정보가 포함 된 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);
}
좋은 출발을해야합니다. 행복한 코딩. 🙂
답변
다이제스트 액세스 인증을 고려할 수 있습니다 . 기본적으로 프로토콜은 다음과 같습니다.
- 클라이언트로부터 요청
- 서버가 고유 한 nonce 문자열로 응답
- 클라이언트는 nonce로 해시 된 사용자 이름 및 비밀번호 (및 기타 값) md5를 제공합니다. 이 해시는 HA1이라고합니다
- 그런 다음 서버는 고객의 신원을 확인하고 요청 된 자료를 제공 할 수 있습니다
- 서버가 새로운 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 사양에 따라 :
자세한 정보는 여기에서 찾을 수 있습니다