Jersey에서 토큰 기반 인증을 활성화하는 방법을 찾고 있습니다. 특정 프레임 워크를 사용하지 않으려 고합니다. 가능합니까?
내 계획은 다음과 같습니다. 사용자가 내 웹 서비스에 가입하면 내 웹 서비스가 토큰을 생성하여 클라이언트로 보내고 클라이언트는이를 유지합니다. 그런 다음 클라이언트는 각 요청에 대해 사용자 이름과 비밀번호 대신 토큰을 보냅니다.
각 요청에 대해 사용자 정의 필터를 사용하려고 생각 @PreAuthorize("hasRole('ROLE')")
했지만 데이터베이스에 대한 많은 요청이 토큰이 유효한지 확인한다고 생각했습니다.
아니면 필터를 만들지 않고 각 요청에 매개 변수 토큰을 넣습니까? 따라서 각 API는 먼저 토큰을 확인한 후 리소스를 검색하기 위해 무언가를 실행합니다.
답변
토큰 기반 인증 작동 방식
토큰 기반 인증에서 클라이언트는 하드 자격 증명 (예 : 사용자 이름 및 비밀번호)을 token 이라는 데이터 조각으로 교환 합니다 . 각 요청에 대해 하드 자격 증명을 보내는 대신 클라이언트는 서버로 토큰을 보내 인증 및 권한 부여를 수행합니다.
간단히 말해서, 토큰 기반 인증 체계는 다음 단계를 따릅니다.
- 클라이언트는 자격 증명 (사용자 이름 및 비밀번호)을 서버로 보냅니다.
- 서버는 자격 증명을 인증하고 유효한 경우 사용자에 대한 토큰을 생성합니다.
- 서버는 이전에 생성 된 토큰을 사용자 식별자 및 만료 날짜와 함께 일부 저장소에 저장합니다.
- 서버는 생성 된 토큰을 클라이언트로 보냅니다.
- 클라이언트는 각 요청에서 서버로 토큰을 보냅니다.
- 각 요청에서 서버는 들어오는 요청에서 토큰을 추출합니다. 토큰으로 서버는 인증을 수행하기 위해 사용자 세부 사항을 찾습니다.
- 토큰이 유효하면 서버는 요청을 수락합니다.
- 토큰이 유효하지 않으면 서버는 요청을 거부합니다.
- 인증이 수행되면 서버는 인증을 수행합니다.
- 서버는 엔드 포인트를 제공하여 토큰을 새로 고칠 수 있습니다.
참고 : 서버가 서명 된 토큰 (예 : JWT와 같이 상태 비 저장 인증 을 수행 할 수있는)을 발행 한 경우 3 단계는 필요하지 않습니다 .
JAX-RS 2.0으로 수행 할 수있는 작업 (Jersey, RESTEasy 및 Apache CXF)
이 솔루션은 공급 업체별 솔루션을 피하면서 JAX-RS 2.0 API 만 사용합니다 . 따라서 Jersey , RESTEasy 및 Apache CXF 와 같은 JAX-RS 2.0 구현에서 작동해야합니다 .
토큰 기반 인증을 사용하는 경우 서블릿 컨테이너가 제공하고 응용 프로그램 web.xml
설명자를 통해 구성 할 수있는 표준 Java EE 웹 응용 프로그램 보안 메커니즘에 의존하지 않는 것이 좋습니다 . 맞춤 인증입니다.
사용자 이름과 비밀번호로 사용자 인증 및 토큰 발행
신임 정보 (사용자 이름 및 비밀번호)를 수신하고 유효성을 검증하고 사용자에 대한 토큰을 발행하는 JAX-RS 자원 메소드를 작성하십시오.
@Path("/authentication")
public class AuthenticationEndpoint {
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response authenticateUser(@FormParam("username") String username,
@FormParam("password") String password) {
try {
// Authenticate the user using the credentials provided
authenticate(username, password);
// Issue a token for the user
String token = issueToken(username);
// Return the token on the response
return Response.ok(token).build();
} catch (Exception e) {
return Response.status(Response.Status.FORBIDDEN).build();
}
}
private void authenticate(String username, String password) throws Exception {
// Authenticate against a database, LDAP, file or whatever
// Throw an Exception if the credentials are invalid
}
private String issueToken(String username) {
// Issue a token (can be a random String persisted to a database or a JWT token)
// The issued token must be associated to a user
// Return the issued token
}
}
신임 정보를 유효성 검증 할 때 예외가 발생하면 상태 403
(금지됨) 의 응답 이 리턴됩니다.
신임 정보의 유효성이 검증되면 상태 200
(OK) 의 응답 이 리턴되고 발행 된 토큰은 응답 페이로드에서 클라이언트로 전송됩니다. 클라이언트는 모든 요청에서 서버로 토큰을 보내야합니다.
를 사용할 때 application/x-www-form-urlencoded
클라이언트는 요청 페이로드에서 다음 형식으로 자격 증명을 보내야합니다.
username=admin&password=123456
폼 매개 변수 대신 사용자 이름과 암호를 클래스로 래핑 할 수 있습니다.
public class Credentials implements Serializable {
private String username;
private String password;
// Getters and setters omitted
}
그런 다음 JSON으로 소비하십시오.
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response authenticateUser(Credentials credentials) {
String username = credentials.getUsername();
String password = credentials.getPassword();
// Authenticate the user, issue a token and return a response
}
이 방법을 사용하면 클라이언트는 요청의 페이로드에서 다음 형식으로 자격 증명을 보내야합니다.
{
"username": "admin",
"password": "123456"
}
요청에서 토큰 추출 및 유효성 검증
클라이언트는 요청의 표준 HTTP Authorization
헤더로 토큰을 보내야합니다 . 예를 들면 다음과 같습니다.
Authorization: Bearer <token-goes-here>
표준 HTTP 헤더의 이름은 권한 부여가 아닌 인증 정보를 전달하기 때문에 불행합니다 . 그러나 자격 증명을 서버로 보내기위한 표준 HTTP 헤더입니다.
JAX-RS는 @NameBinding
필터 및 인터셉터를 자원 클래스 및 메소드에 바인드하기 위해 다른 주석을 작성하는 데 사용되는 메타 주석 인을 제공 합니다. @Secured
다음과 같이 주석을 정의하십시오 .
@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Secured { }
위에서 정의한 이름 바인딩 어노테이션은 필터 클래스를 장식하는 데 사용됩니다.이 클래스는을 구현 ContainerRequestFilter
하여 요청이 자원 메소드에 의해 처리되기 전에 인터셉트 할 수 있도록합니다. 는 ContainerRequestContext
HTTP 요청 헤더에 액세스 한 후 토큰을 추출하는 데 사용할 수 있습니다 :
@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
private static final String REALM = "example";
private static final String AUTHENTICATION_SCHEME = "Bearer";
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Get the Authorization header from the request
String authorizationHeader =
requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
// Validate the Authorization header
if (!isTokenBasedAuthentication(authorizationHeader)) {
abortWithUnauthorized(requestContext);
return;
}
// Extract the token from the Authorization header
String token = authorizationHeader
.substring(AUTHENTICATION_SCHEME.length()).trim();
try {
// Validate the token
validateToken(token);
} catch (Exception e) {
abortWithUnauthorized(requestContext);
}
}
private boolean isTokenBasedAuthentication(String authorizationHeader) {
// Check if the Authorization header is valid
// It must not be null and must be prefixed with "Bearer" plus a whitespace
// The authentication scheme comparison must be case-insensitive
return authorizationHeader != null && authorizationHeader.toLowerCase()
.startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " ");
}
private void abortWithUnauthorized(ContainerRequestContext requestContext) {
// Abort the filter chain with a 401 status code response
// The WWW-Authenticate header is sent along with the response
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE,
AUTHENTICATION_SCHEME + " realm=\"" + REALM + "\"")
.build());
}
private void validateToken(String token) throws Exception {
// Check if the token was issued by the server and if it's not expired
// Throw an Exception if the token is invalid
}
}
토큰 유효성 검사 중 문제가 발생하면 상태 401
(무단) 로 응답 이 반환됩니다. 그렇지 않으면 요청이 자원 메소드로 진행됩니다.
REST 엔드 포인트 보안
인증 필터를 자원 메소드 또는 자원 클래스에 바인드하려면 @Secured
위에 작성된 주석으로 주석을 답니다 . 주석이 달린 메소드 및 / 또는 클래스의 경우 필터가 실행됩니다. 이는 요청이 유효한 토큰으로 수행되는 경우 에만 엔드 포인트에 도달 함을 의미합니다 .
일부 메소드 또는 클래스에 인증이 필요하지 않은 경우 주석을 달지 마십시오.
@Path("/example")
public class ExampleResource {
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response myUnsecuredMethod(@PathParam("id") Long id) {
// This method is not annotated with @Secured
// The authentication filter won't be executed before invoking this method
...
}
@DELETE
@Secured
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response mySecuredMethod(@PathParam("id") Long id) {
// This method is annotated with @Secured
// The authentication filter will be executed before invoking this method
// The HTTP request must be performed with a valid token
...
}
}
상기 도시 된 예에서, 필터는 실행될 만 위해 mySecuredMethod(Long)
이 주석 있기 때문에 방법 @Secured
.
현재 사용자 식별
요청을 다시 수행하는 사용자에게 REST API를 다시 알아야 할 가능성이 큽니다. 이를 달성하기 위해 다음과 같은 접근법을 사용할 수 있습니다.
현재 요청의 보안 컨텍스트 재정의
ContainerRequestFilter.filter(ContainerRequestContext)
메소드 내 SecurityContext
에서 현재 요청에 대해 새 인스턴스를 설정할 수 있습니다. 그런 다음을 재정 의하여 인스턴스를 SecurityContext.getUserPrincipal()
반환하십시오 Principal
.
final SecurityContext currentSecurityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext() {
@Override
public Principal getUserPrincipal() {
return () -> username;
}
@Override
public boolean isUserInRole(String role) {
return true;
}
@Override
public boolean isSecure() {
return currentSecurityContext.isSecure();
}
@Override
public String getAuthenticationScheme() {
return AUTHENTICATION_SCHEME;
}
});
토큰을 사용하여의 사용자 식별자 (사용자 이름)를 찾습니다 Principal
.
SecurityContext
JAX-RS 자원 클래스에 다음을 삽입하십시오 .
@Context
SecurityContext securityContext;
JAX-RS 자원 메소드에서도 동일하게 수행 할 수 있습니다.
@GET
@Secured
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response myMethod(@PathParam("id") Long id,
@Context SecurityContext securityContext) {
...
}
그런 다음 Principal
:
Principal principal = securityContext.getUserPrincipal();
String username = principal.getName();
CDI (컨텍스트 및 종속성 주입) 사용
어떤 이유로을 재정의하지 않으려 SecurityContext
는 경우 이벤트 및 제작자와 같은 유용한 기능을 제공하는 CDI (컨텍스트 및 종속성 주입)를 사용할 수 있습니다.
CDI 규정자를 작성하십시오.
@Qualifier
@Retention(RUNTIME)
@Target({ METHOD, FIELD, PARAMETER })
public @interface AuthenticatedUser { }
당신이에서 AuthenticationFilter
주입, 위에서 만든 Event
주석 @AuthenticatedUser
:
@Inject
@AuthenticatedUser
Event<String> userAuthenticatedEvent;
인증이 성공하면 username을 매개 변수로 전달하여 이벤트를 발생시킵니다 (사용자에게 토큰이 발급되고 토큰이 사용자 식별자를 찾는 데 사용됨).
userAuthenticatedEvent.fire(username);
응용 프로그램의 사용자를 나타내는 클래스가있을 가능성이 큽니다. 이 클래스를 호출하자 User
.
인증 이벤트를 처리 할 CDI Bean을 작성 User
하고 해당 사용자 이름 으로 인스턴스를 찾아 authenticatedUser
생산자 필드에 지정하십시오 .
@RequestScoped
public class AuthenticatedUserProducer {
@Produces
@RequestScoped
@AuthenticatedUser
private User authenticatedUser;
public void handleAuthenticationEvent(@Observes @AuthenticatedUser String username) {
this.authenticatedUser = findUser(username);
}
private User findUser(String username) {
// Hit the the database or a service to find a user by its username and return it
// Return the User instance
}
}
이 authenticatedUser
필드는 User
JAX-RS 서비스, CDI Bean, 서블릿 및 EJB와 같은 컨테이너 관리 Bean에 주입 될 수 있는 인스턴스를 생성합니다 . 다음 코드를 사용하여 User
인스턴스 를 주입하십시오 (실제로 CDI 프록시 임).
@Inject
@AuthenticatedUser
User authenticatedUser;
CDI @Produces
주석은 JAX-RS 주석과 다릅니다@Produces
.
- CDI :
javax.enterprise.inject.Produces
- JAX-RS :
javax.ws.rs.Produces
Bean @Produces
에서 CDI 주석 을 사용해야합니다 AuthenticatedUserProducer
.
여기서 핵심은로 주석이 달린 Bean으로 @RequestScoped
, 필터와 Bean간에 데이터를 공유 할 수 있습니다. 이벤트를 사용하지 않으려면 인증 된 사용자를 요청 범위 Bean에 저장하도록 필터를 수정 한 후 JAX-RS 자원 클래스에서 읽을 수 있습니다.
SecurityContext
CDI 접근 방식은 을 대체하는 접근 방식과 비교하여 JAX-RS 자원 및 제공자 이외의 Bean에서 인증 된 사용자를 가져올 수 있습니다.
역할 기반 인증 지원
역할 기반 인증을 지원하는 방법에 대한 자세한 내용은 다른 답변 을 참조하십시오 .
발행 토큰
토큰은 다음과 같습니다.
- 불투명 : 임의의 문자열과 같이 값 자체 이외의 세부 정보를 표시하지 않습니다 .
- 자체 포함 : JWT와 같은 토큰 자체에 대한 세부 사항을 포함합니다.
아래 세부 사항을 참조하십시오.
토큰으로 임의의 문자열
임의의 문자열을 생성하고 사용자 식별자 및 만료 날짜와 함께 토큰을 데이터베이스에 유지함으로써 토큰을 발행 할 수 있습니다. Java에서 임의의 문자열을 생성하는 방법에 대한 좋은 예는 여기에서 볼 수 있습니다 . 당신은 또한 사용할 수 있습니다 :
Random random = new SecureRandom();
String token = new BigInteger(130, random).toString(32);
JWT (JSON 웹 토큰)
JWT (JSON Web Token)는 두 당사자 간의 클레임을 안전하게 표현하는 표준 방법이며 RFC 7519에 의해 정의됩니다 .
자체 포함 토큰이며 클레임에 세부 정보를 저장할 수 있습니다 . 이러한 클레임은 토큰 페이로드에 저장되며 이는 토큰이 Base64로 인코딩 된 JSON 입니다. 다음은 RFC 7519에 등록 된 클레임 과 그 의미에 대한 것입니다 (자세한 내용은 전체 RFC를 읽으십시오).
iss
: 토큰을 발행 한 교장.sub
: JWT의 주제 인 교장.exp
: 토큰의 만료 날짜입니다.nbf
: 토큰 처리가 시작되는 시간입니다.iat
: 토큰이 발행 된 시간입니다.jti
: 토큰의 고유 식별자입니다.
비밀번호와 같은 민감한 데이터를 토큰에 저장해서는 안됩니다.
클라이언트가 페이로드를 읽을 수 있으며 서버에서 서명을 확인하여 토큰의 무결성을 쉽게 확인할 수 있습니다. 서명은 토큰이 변조되는 것을 방지합니다.
JWT 토큰을 추적 할 필요가없는 경우 JWT 토큰을 유지하지 않아도됩니다. 그러나 토큰을 유지하면 토큰 액세스를 무효화하고 취소 할 수 있습니다. JWT 토큰을 추적하려면 서버에서 전체 토큰을 유지하는 대신 토큰 jti
을 발급 한 사용자, 만료 날짜 등과 같은 다른 세부 정보와 함께 토큰 식별자 ( 클레임)를 유지할 수 있습니다 .
토큰을 유지하는 경우 데이터베이스가 무한정 커지지 않도록 항상 이전 토큰을 제거하십시오.
JWT 사용
다음과 같은 JWT 토큰을 발행하고 유효성을 검증하는 몇 가지 Java 라이브러리가 있습니다.
JWT와 함께 작동하는 다른 훌륭한 자료를 찾으려면 http://jwt.io를 참조하십시오 .
JWT로 토큰 해지 처리
토큰을 취소하려면 추적해야합니다. 전체 토큰을 서버 측에 저장할 필요는 없으며 토큰 식별자 (고유해야 함)와 필요한 경우 일부 메타 데이터 만 저장하십시오. 토큰 식별자로는 UUID를 사용할 수 있습니다 .
jti
제는 토큰의 토큰 식별자를 저장하는 데 사용되어야한다. 토큰의 유효성을 검사 할 때 jti
서버 측에있는 토큰 식별자와 비교하여 클레임 값을 확인하여 토큰이 취소되지 않았는지 확인하십시오 .
보안을 위해 사용자가 비밀번호를 변경할 때 모든 토큰을 취소하십시오.
추가 정보
답변
이 답변은 인증 에 관한 것이며 인증 에 대한 이전 답변을 보완 합니다.
왜 또 다른 대답? JSR-250 주석을 지원하는 방법에 대한 세부 정보를 추가하여 이전 답변을 확장하려고했습니다. 그러나 원래의 대답은 너무 길어져 최대 길이 30,000자를 초과했습니다 . 따라서 전체 권한 부여 세부 정보를이 답변으로 옮겼으며 다른 답변은 인증 및 토큰 발급에 중점을 두었습니다.
@Secured
주석 으로 역할 기반 인증 지원
다른 답변에 표시된 인증 흐름 외에도 REST 엔드 포인트에서 역할 기반 권한 부여를 지원할 수 있습니다.
열거를 작성하고 필요에 따라 역할을 정의하십시오.
public enum Role {
ROLE_1,
ROLE_2,
ROLE_3
}
@Secured
역할을 지원하기 위해 이전에 작성된 이름 바인딩 주석을 변경하십시오 .
@NameBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Secured {
Role[] value() default {};
}
그런 다음 @Secured
권한 부여를 수행하기 위해 자원 클래스 및 메소드에 주석을 추가하십시오 . 메소드 어노테이션은 클래스 어노테이션을 대체합니다.
@Path("/example")
@Secured({Role.ROLE_1})
public class ExampleResource {
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response myMethod(@PathParam("id") Long id) {
// This method is not annotated with @Secured
// But it's declared within a class annotated with @Secured({Role.ROLE_1})
// So it only can be executed by the users who have the ROLE_1 role
...
}
@DELETE
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
@Secured({Role.ROLE_1, Role.ROLE_2})
public Response myOtherMethod(@PathParam("id") Long id) {
// This method is annotated with @Secured({Role.ROLE_1, Role.ROLE_2})
// The method annotation overrides the class annotation
// So it only can be executed by the users who have the ROLE_1 or ROLE_2 roles
...
}
}
AUTHORIZATION
우선 순위 필터를 작성하십시오 AUTHENTICATION
. 우선 순위 필터는 이전에 정의 된 우선 순위 필터 이후에 실행 됩니다.
를 ResourceInfo
사용 하여 요청을 처리 할 자원 Method
과 자원 Class
을 확보 한 후 @Secured
주석 을 추출 할 수 있습니다.
@Secured
@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {
@Context
private ResourceInfo resourceInfo;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Get the resource class which matches with the requested URL
// Extract the roles declared by it
Class<?> resourceClass = resourceInfo.getResourceClass();
List<Role> classRoles = extractRoles(resourceClass);
// Get the resource method which matches with the requested URL
// Extract the roles declared by it
Method resourceMethod = resourceInfo.getResourceMethod();
List<Role> methodRoles = extractRoles(resourceMethod);
try {
// Check if the user is allowed to execute the method
// The method annotations override the class annotations
if (methodRoles.isEmpty()) {
checkPermissions(classRoles);
} else {
checkPermissions(methodRoles);
}
} catch (Exception e) {
requestContext.abortWith(
Response.status(Response.Status.FORBIDDEN).build());
}
}
// Extract the roles from the annotated element
private List<Role> extractRoles(AnnotatedElement annotatedElement) {
if (annotatedElement == null) {
return new ArrayList<Role>();
} else {
Secured secured = annotatedElement.getAnnotation(Secured.class);
if (secured == null) {
return new ArrayList<Role>();
} else {
Role[] allowedRoles = secured.value();
return Arrays.asList(allowedRoles);
}
}
}
private void checkPermissions(List<Role> allowedRoles) throws Exception {
// Check if the user contains one of the allowed roles
// Throw an Exception if the user has not permission to execute the method
}
}
사용자에게 작업을 실행할 권한이 없으면 403
(Forbidden)으로 요청이 중단됩니다 .
요청을 수행하는 사용자를 알려면 이전 답변을 참조하십시오 . 당신은 그것을 얻을 수있는 SecurityContext
(이미 설정되어야하는 ContainerRequestContext
당신이 갈 접근 방식에 따라 CDI를 사용하거나 주입).
경우 @Secured
주석이 어떤 역할을 선언 없습니다, 당신은 사용자가 가지고있는 역할을 무시하고, 인증 된 모든 사용자가 해당 엔드 포인트에 액세스 할 수 있습니다 가정 할 수있다.
JSR-250 주석으로 역할 기반 권한 부여 지원
대안의 역할을 정의하는 @Secured
위 그림과 같이 주석을, 당신은 같은 JSR-250 주석을 고려할 수 @RolesAllowed
, @PermitAll
과 @DenyAll
.
JAX-RS는 기본적으로 이러한 주석을 지원하지 않지만 필터를 사용하여 달성 할 수 있습니다. 다음 사항을 모두 지원하려면 명심해야 할 몇 가지 사항이 있습니다.
@DenyAll
방법에 대한보다 우선@RolesAllowed
하고@PermitAll
클래스에.@RolesAllowed
메소드@PermitAll
에서 클래스 보다 우선 합니다.@PermitAll
메소드@RolesAllowed
에서 클래스 보다 우선 합니다.@DenyAll
수업에 첨부 할 수 없습니다.@RolesAllowed
클래스에서 클래스보다 우선@PermitAll
합니다.
따라서 JSR-250 주석을 확인하는 인증 필터는 다음과 같습니다.
@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {
@Context
private ResourceInfo resourceInfo;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Method method = resourceInfo.getResourceMethod();
// @DenyAll on the method takes precedence over @RolesAllowed and @PermitAll
if (method.isAnnotationPresent(DenyAll.class)) {
refuseRequest();
}
// @RolesAllowed on the method takes precedence over @PermitAll
RolesAllowed rolesAllowed = method.getAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
performAuthorization(rolesAllowed.value(), requestContext);
return;
}
// @PermitAll on the method takes precedence over @RolesAllowed on the class
if (method.isAnnotationPresent(PermitAll.class)) {
// Do nothing
return;
}
// @DenyAll can't be attached to classes
// @RolesAllowed on the class takes precedence over @PermitAll on the class
rolesAllowed =
resourceInfo.getResourceClass().getAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
performAuthorization(rolesAllowed.value(), requestContext);
}
// @PermitAll on the class
if (resourceInfo.getResourceClass().isAnnotationPresent(PermitAll.class)) {
// Do nothing
return;
}
// Authentication is required for non-annotated methods
if (!isAuthenticated(requestContext)) {
refuseRequest();
}
}
/**
* Perform authorization based on roles.
*
* @param rolesAllowed
* @param requestContext
*/
private void performAuthorization(String[] rolesAllowed,
ContainerRequestContext requestContext) {
if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) {
refuseRequest();
}
for (final String role : rolesAllowed) {
if (requestContext.getSecurityContext().isUserInRole(role)) {
return;
}
}
refuseRequest();
}
/**
* Check if the user is authenticated.
*
* @param requestContext
* @return
*/
private boolean isAuthenticated(final ContainerRequestContext requestContext) {
// Return true if the user is authenticated or false otherwise
// An implementation could be like:
// return requestContext.getSecurityContext().getUserPrincipal() != null;
}
/**
* Refuse the request.
*/
private void refuseRequest() {
throw new AccessDeniedException(
"You don't have permissions to perform this action.");
}
}
참고 : 위 구현은 Jersey 기반입니다 RolesAllowedDynamicFeature
. Jersey를 사용하는 경우 고유 한 필터를 작성할 필요가 없으며 기존 구현 만 사용하십시오.