커스텀 토큰을 사용한 REST API 보안 보호(스테이트리스, UI, 쿠키, 기본 인증, OAuth, 로그인 페이지 없음)
Spring Security에서 REST API를 보호하는 방법을 보여주는 가이드라인이나 샘플코드가 많이 있지만 대부분은 웹 클라이언트를 상정하고 로그인 페이지, 리다이렉션, 쿠키 사용 등에 대해 이야기합니다.HTTP 헤더에서 커스텀토큰을 체크하는 단순한 필터로도 충분할 수 있습니다.다음 요건을 충족하기 위해 보안을 구현하려면 어떻게 해야 합니까?같은 작업을 하고 있는 GIST/GITHUB 프로젝트가 있습니까?스프링 보안에 대한 저의 지식은 한정되어 있기 때문에, 스프링 보안에 대해 보다 간단하게 실장할 수 있는 방법이 있으면 가르쳐 주세요.
- HTTPS를 통한 스테이트리스 백엔드에서 제공하는 REST API
- 클라이언트는 웹 앱, 모바일 앱, 임의의 SPA 스타일 앱, 서드파티 API일 수 있습니다.
- 기본 인증 없음, 쿠키 없음, UI 없음(JSP/HTML/static-resources 없음), 리다이렉션 없음, OAuth 공급자 없음.
- HTTPS 헤더에 설정된 사용자 정의 토큰
- 외부 스토어에 대한 토큰 검증(MemCached/Redis/ 또는 RDBMS 등)
- 선택한 경로(/login, /signup, /public 등)를 제외한 모든 API를 인증해야 합니다.
Springboot, Spring Security 등을 사용합니다.Java 구성 솔루션 선호(XML 없음)
샘플 앱은 바로 이 작업을 수행합니다. 상태 비저장 시나리오에서 Spring Security를 사용하여 REST 엔드포인트를 보호합니다.개개의 REST 콜은 HTTP 헤더를 사용하여 인증됩니다.인증 정보는 서버 측에 인메모리 캐시에 격납되어 일반적인 웹 어플리케이션의 HTTP 세션에 의해 제공되는 것과 동일한 의미를 제공합니다.이 앱은 최소한의 커스텀 코드로 완전한 Spring Security 인프라스트럭처를 사용합니다.스프링 보안 인프라스트럭처 외부에서는 필터도 코드도 사용할 수 없습니다.
기본 개념은 다음 4개의 스프링보안 컴포넌트를 구현하는 것입니다.
org.springframework.security.web.AuthenticationEntryPoint
REST 콜을 트랩하는데 필요한 인증 토큰이 없어 요구를 거부합니다.org.springframework.security.core.Authentication
REST API에 필요한 인증 정보를 유지합니다.org.springframework.security.authentication.AuthenticationProvider
(데이터베이스, LDAP 서버, 웹 서비스 등에 대해) 실제 인증을 수행합니다.org.springframework.security.web.context.SecurityContextRepository
HTTP를 사용합니다.EHCACHE의 EHCACHE의 EHCACHE의 EHCACHE의 EHCACHE가 있습니다.
이 예에서는 XML 설정을 사용하고 있습니다만, 동등한 Java 설정을 간단하게 작성할 수 있습니다.
맞아, 쉽지 않고 좋은 예가 많지 않아.제가 본 예시는 다른 스프링 보안 용품을 나란히 사용할 수 없도록 만들었습니다.저도 최근에 비슷한 일을 했어요. 제가 한 일은 이렇습니다.
헤더 값을 보유하려면 사용자 지정 토큰이 필요합니다.
public class CustomToken extends AbstractAuthenticationToken {
private final String value;
//Getters and Constructor. Make sure getAutheticated returns false at first.
//I made mine "immutable" via:
@Override
public void setAuthenticated(boolean isAuthenticated) {
//It doesn't make sense to let just anyone set this token to authenticated, so we block it
//Similar precautions are taken in other spring framework tokens, EG: UsernamePasswordAuthenticationToken
if (isAuthenticated) {
throw new IllegalArgumentException(MESSAGE_CANNOT_SET_AUTHENTICATED);
}
super.setAuthenticated(false);
}
}
헤더를 추출하여 관리자에게 인증하도록 요청하려면 스프링 보안 필터가 필요합니다.이렇게 강조된 텍스트는
public class CustomFilter extends AbstractAuthenticationProcessingFilter {
public CustomFilter(RequestMatcher requestMatcher) {
super(requestMatcher);
this.setAuthenticationSuccessHandler((request, response, authentication) -> {
/*
* On success the desired action is to chain through the remaining filters.
* Chaining is not possible through the success handlers, because the chain is not accessible in this method.
* As such, this success handler implementation does nothing, and chaining is accomplished by overriding the successfulAuthentication method as per:
* http://docs.spring.io/autorepo/docs/spring-security/3.2.4.RELEASE/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#successfulAuthentication(javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse,%20javax.servlet.FilterChain,%20org.springframework.security.core.Authentication)
* "Subclasses can override this method to continue the FilterChain after successful authentication."
*/
});
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
String tokenValue = request.getHeader("SOMEHEADER");
if(StringUtils.isEmpty(tokenValue)) {
//Doing this check is kinda dumb because we check for it up above in doFilter
//..but this is a public method and we can't do much if we don't have the header
//also we can't do the check only here because we don't have the chain available
return null;
}
CustomToken token = new CustomToken(tokenValue);
token.setDetails(authenticationDetailsSource.buildDetails(request));
return this.getAuthenticationManager().authenticate(token);
}
/*
* Overriding this method to maintain the chaining on authentication success.
* http://docs.spring.io/autorepo/docs/spring-security/3.2.4.RELEASE/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#successfulAuthentication(javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse,%20javax.servlet.FilterChain,%20org.springframework.security.core.Authentication)
* "Subclasses can override this method to continue the FilterChain after successful authentication."
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
//if this isn't called, then no auth is set in the security context holder
//and subsequent security filters can still execute.
//so in SOME cases you might want to conditionally call this
super.successfulAuthentication(request, response, chain, authResult);
//Continue the chain
chain.doFilter(request, response);
}
}
스프링 보안 체인에 사용자 지정 필터 등록
@Configuration
public static class ResourceEndpointsSecurityConfig extends WebSecurityConfigurerAdapter {
//Note, we don't register this as a bean as we don't want it to be added to the main Filter chain, just the spring security filter chain
protected AbstractAuthenticationProcessingFilter createCustomFilter() throws Exception {
CustomFilter filter = new CustomFilter( new RegexRequestMatcher("^/.*", null));
filter.setAuthenticationManager(this.authenticationManagerBean());
return filter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//fyi: This adds it to the spring security proxy filter chain
.addFilterBefore(createCustomFilter(), AnonymousAuthenticationFilter.class)
}
}
필터로 추출된 토큰을 검증하는 커스텀 인증 프로바이더.
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication auth)
throws AuthenticationException {
CustomToken token = (CustomToken)auth;
try{
//Authenticate token against redis or whatever you want
//This i found weird, you need a Principal in your Token...I use User
//I found this to be very redundant in spring security, but Controller param resolving will break if you don't do this...anoying
org.springframework.security.core.userdetails.User principal = new User(...);
//Our token resolved to a username so i went with this token...you could make your CustomToken take the principal. getCredentials returns "NO_PASSWORD"..it gets cleared out anyways. also the getAuthenticated for the thing you return should return true now
return new UsernamePasswordAuthenticationToken(principal, auth.getCredentials(), principal.getAuthorities());
} catch(Expection e){
//TODO throw appropriate AuthenticationException types
throw new BadCredentialsException(MESSAGE_AUTHENTICATION_FAILURE, e);
}
}
@Override
public boolean supports(Class<?> authentication) {
return CustomToken.class.isAssignableFrom(authentication);
}
}
마지막으로 인증 매니저가 일부 @Configuration 클래스에서 프로바이더를 찾을 수 있도록 프로바이더를 bean으로 등록합니다.@Component it도 이 방법을 선호합니다.
@Bean
public AuthenticationProvider createCustomAuthenticationProvider(injectedDependencies) {
return new CustomAuthenticationProvider(injectedDependencies);
}
이 코드는 모든 엔드포인트를 보호합니다. Boot 를 사용하여 됩니다.또한 Spring Boot Starter Security를 사용하여 정의해야 .UserDetailsService
당신이 지나쳐가는 곳AuthenticationManagerBuilder
.
쇼트 페이스트EmbeddedRedisConfiguration
★★★★★★★★★★★★★★★★★」SecurityConfig
를 치환합니다.AuthenticationManagerBuilder
츠키노
HTTP:
요청 토큰 - 요청 헤더에 기본 HTTP 인증 내용을 보냅니다.토큰은 응답 헤더로 반환됩니다.
http --print=hH -a user:password localhost:8080/v1/users
GET /v1/users HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Basic dXNlcjpwYXNzd29yZA==
Connection: keep-alive
Host: localhost:8080
User-Agent: HTTPie/0.9.3
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 4
Content-Type: text/plain;charset=UTF-8
Date: Fri, 06 May 2016 09:44:23 GMT
Expires: 0
Pragma: no-cache
Server: Apache-Coyote/1.1
X-Application-Context: application
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
x-auth-token: cacf4a97-75fe-464d-b499-fcfacb31c8af
같은 요청이지만 토큰 사용:
http --print=hH localhost:8080/v1/users 'x-auth-token: cacf4a97-75fe-464d-b499-fcfacb31c8af'
GET /v1/users HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8080
User-Agent: HTTPie/0.9.3
x-auth-token: cacf4a97-75fe-464d-b499-fcfacb31c8af
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 4
Content-Type: text/plain;charset=UTF-8
Date: Fri, 06 May 2016 09:44:58 GMT
Expires: 0
Pragma: no-cache
Server: Apache-Coyote/1.1
X-Application-Context: application
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
잘못된 사용자 이름/비밀번호 또는 토큰을 전달하면 401이 반환됩니다.
자바
는 그 를 나는에 했다.build.gradle
compile("org.springframework.session:spring-session-data-redis:1.0.1.RELEASE")
compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework.boot:spring-boot-starter-web")
compile("com.github.kstyrc:embedded-redis:0.6")
다음으로 Redis 설정
@Configuration
@EnableRedisHttpSession
public class EmbeddedRedisConfiguration {
private static RedisServer redisServer;
@Bean
public JedisConnectionFactory connectionFactory() throws IOException {
redisServer = new RedisServer(Protocol.DEFAULT_PORT);
redisServer.start();
return new JedisConnectionFactory();
}
@PreDestroy
public void destroy() {
redisServer.stop();
}
}
보안 구성:
@Configuration
@EnableWebSecurity
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic();
}
@Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
}
.AuthenticationManagerBuilder
를 사용합니다.inMemoryAuthentication
자민당클래스의 정의를 확인해 주세요.하고 userDetailsService
, 「」가 필요합니다.UserDetailsService
★★★★★★ 。
마지막으로 리리지 service service 를 사용한 사용자 서비스CrudRepository
.
@Service
public class UserService implements UserDetailsService {
@Autowired
UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserAccount userAccount = userRepository.findByEmail(username);
if (userAccount == null) {
return null;
}
return new User(username, userAccount.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
}
JWT - Jhipster를 사용한 또 다른 프로젝트 예시
JHipster를 사용하여 Microservice 응용 프로그램을 생성해 보십시오.Spring Security와 JWT가 즉시 통합되어 템플릿을 생성합니다.
https://jhipster.github.io/security/
JSON Web Tokens http://jwt.io/을 추천합니다.이것은 스테이트리스로 확장 가능합니다.
다음은 프로젝트 예시입니다.https://github.com/brahalla/Cerberus
언급URL : https://stackoverflow.com/questions/25317405/securing-rest-api-using-custom-tokens-stateless-no-ui-no-cookies-no-basic-au
'programing' 카테고리의 다른 글
스프링 부트 2.4.0 버전에 프로파일 포함 (0) | 2023.03.20 |
---|---|
Angular에서 전역 js 변수에 액세스하는 방법JS 지시어 (0) | 2023.03.15 |
Angular 5 서비스를 통해 로컬 .json 파일을 읽습니다. (0) | 2023.03.15 |
jeast.fn()의 기능과 사용법은 무엇입니까? (0) | 2023.03.15 |
MongoDB에서 가장 큰 문서 크기 찾기 (0) | 2023.03.15 |