GP Coder

Trang chia sẻ kiến thức lập trình Java

  • Java Core
    • Basic Java
    • OOP
    • Exception Handling
    • Multi-Thread
    • Java I/O
    • Networking
    • Reflection
    • Collection
    • Java 8
  • Design pattern
    • Creational Pattern
    • Structuaral Pattern
    • Behavior Pattern
  • Web Service
    • SOAP
    • REST
  • JPA
  • Java library
    • Report
    • Json
    • Unit Test
  • Message Queue
    • ActiveMQ
    • RabbitMQ
  • All
Trang chủ Java Webservice REST Web service: Basic Authentication trong Jersey 2.x

REST Web service: Basic Authentication trong Jersey 2.x

Đăng vào 03/07/2019 . Được đăng bởi GP Coder . 5165 Lượt xem . Toàn màn hình

Trong bài trước, chúng ta đã cùng tìm hiểu về xác thực và phân quyền trong ứng dụng. Trong bài này, chúng ta sẽ cùng tìm hiểu về xác thực và phân quyền ứng dụng sử dụng cơ chế Basic Authentication trong Jersey 2.x.

Nội dung

  • 1 Giới thiệu Basic Authentication trong Jersey REST API
  • 2 Basic Authentication trong Jersey Server
  • 3 Basic Authentication trong Jersey Client

Giới thiệu Basic Authentication trong Jersey REST API

Basic Authentication là cơ chế xác thực mà ứng dụng client sẽ gửi username + password của người dùng theo mỗi request lên server.

Trong bài “Filter và Interceptor với Jersey 2.x“, chúng ta đã biết Filter có thể thực hiện một số hành động trước khi resource method được thực thi. Phía server có thể dễ dàng chứng thực tất cả request của client trước khi REST API method thực sự được gọi thông qua tính năng Filter này.

Ý tưởng như sau:

  • Tạo một Filter implements từ ContainerRequestFilter : để verify access của một user.
  • Đánh dấu Annotation: @RolesAllowed hoặc @PermitAll hoặc @DenyAll ở mức class hoặc method để xác thực quyền truy cập resource.
    • @RolesAllowed : chỉ định một số role được phép gọi resource method.
    • @PermitAll : bất kỳ user nào được phép gọi resource method.
    • @DenyAll : bất kỳ user nào cũng không được phép gọi resource method.

Basic Authentication trong Jersey Server

Tạo Jersey project

Tương tự như các bài viết trước, chúng ta sẽ tạo Jersey project với cấu trúc như sau:

Tạo các data model class

Order.java


package com.gpcoder.model;

import javax.xml.bind.annotation.XmlRootElement;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement
public class Order {

	private Integer id;
	private String name;
}

User.java


package com.gpcoder.model;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

	private String username;
	private String password;
	private List<String> roles;
}

Role.java


package com.gpcoder.model;

public interface Role {

	String ROLE_ADMIN = "Admin";
	String ROLE_CUSTOMER = "Customer";
}

Tạo SecurityContext class

SecurityContext : chứa thông tin chứng thực của một request. Sau khi chứng thực thành công, chúng ta cần cung cấp thông tin chứng thực user, role cho context class này. Các phương thức của SecurityContext sẽ được triệu gọi trước khi các resource method đã đánh dấu với các Annotation @RolesAllowed hoặc @PermitAll hoặc @DenyAll được thực thi.

BasicSecurityConext.java


package com.gpcoder.model;

import java.security.Principal;

import javax.ws.rs.core.SecurityContext;

/**
 * The SecurityContext interface provides access to security related
 * information. An instance of SecurityContext can be injected into a JAX-RS
 * resource class field or method parameter using the @Context annotation.
 * 
 * @see https://jersey.github.io/documentation/latest/security.html
 * @see https://docs.oracle.com/javaee/7/api/javax/ws/rs/core/SecurityContext.html
 */
public class BasicSecurityConext implements SecurityContext {

	private User user;
	private boolean secure;

	public BasicSecurityConext(User user, boolean secure) {
		this.user = user;
		this.secure = secure;
	}

	@Override
	public Principal getUserPrincipal() {
		return () -> user.getUsername();
	}

	@Override
	public boolean isUserInRole(String role) {
		return user.getRoles().contains(role);
	}

	@Override
	public boolean isSecure() {
		return secure;
	}

	@Override
	public String getAuthenticationScheme() {
		return SecurityContext.BASIC_AUTH;
	}
}

Tạo REST API

Chúng ta cung cấp các REST API như sau:

OrderService.java


package com.gpcoder.api;

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;

import com.gpcoder.model.Order;
import com.gpcoder.model.Role;

// URI:
// http(s)://<domain>:(port)/<YourApplicationName>/<UrlPattern in web.xml>/<path>
// http://localhost:8080/RestfulWebServiceExample/rest/orders
@Path("/orders")
@PermitAll
public class OrderService {

	@GET
	@Path("/{id}")
	public Response get(@PathParam("id") int id) {
		System.out.println("OrderService#get()");
		return Response.ok("OrderService#get()").build();
	}

	@RolesAllowed(Role.ROLE_CUSTOMER)
	@POST
	public Response insert(Order order, @Context SecurityContext securityContext) {
		System.out.println("User: " + securityContext.getUserPrincipal().getName());
		System.out.println("OrderService#insert()");
		return Response.ok("OrderService#insert()").build();
	}

	@RolesAllowed({ Role.ROLE_ADMIN, Role.ROLE_CUSTOMER })
	@PUT
	public Response update(Order order) {
		System.out.println("OrderService#update()");
		return Response.ok("OrderService#update()").build();
	}

	@RolesAllowed(Role.ROLE_ADMIN)
	@DELETE
	@Path("/{id}")
	public Response delete(@PathParam("id") int id) {
		System.out.println("OrderService#delete()");
		return Response.ok("OrderService#delete()").build();
	}
}

Tạo ContainerRequestFilter để chứng thực user

Chúng ta sử dụng ContainerRequestFilter để thực hiện chứng thực user trước khi truy cập resource method.

  • Lấy thông tin Authorization từ header ở phía client gửi lên.
  • Tách lấy username và password từ Authentication header.
  • Truy xuất thông tin user từ database dựa trên username client gửi lên.
  • Thực hiện chứng thực user.
  • Lưu thông tin chứng thực lại để các request sau có thể kiểm tra chứng thực mà không cần truy xuất database, chẳng hạn @RolesAllowed.

package com.gpcoder.filter;

import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.StringTokenizer;

import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;

import com.gpcoder.model.BasicSecurityConext;
import com.gpcoder.model.User;
import com.gpcoder.service.UserService;

/**
 * This filter verify the access permissions for a user based on username and
 * password provided in request
 */
@Provider
@Priority(Priorities.AUTHENTICATION) // needs to happen before authorization
public class AuthFilter implements ContainerRequestFilter {

	@Override
	public void filter(ContainerRequestContext containerRequest)
			throws WebApplicationException, UnsupportedEncodingException {

		// (1) Parsing the Basic Auth Authorization header
		// The structure of authentication header:
		// Authorization: Basic encodedByBase64(username:password)
		String authCredentials = containerRequest.getHeaderString(HttpHeaders.AUTHORIZATION);
		if (null == authCredentials) {
			return;
		}

		// (2) Extract user name and password from Authentication header
		final String encodedUserPassword = authCredentials.replaceFirst("Basic" + " ", "");
		byte[] decodedBytes = Base64.getDecoder().decode(encodedUserPassword);
		String usernameAndPassword = new String(decodedBytes, "UTF-8");

		final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
		final String username = tokenizer.nextToken();
		final String password = tokenizer.nextToken();

		// (3) Getting the User with the username
		UserService userService = new UserService();
		User user = userService.getUser(username);

		// (4) Doing authentication
		if (user == null || !user.getPassword().equals(password)) {
			Response respone = Response.status(Response.Status.UNAUTHORIZED) // 401 Unauthorized
					.entity("You cannot access this resource") // the response entity
					.build();
			containerRequest.abortWith(respone);
		}

		// (5) Setting a new SecurityContext
		SecurityContext oldContext = containerRequest.getSecurityContext();
		containerRequest.setSecurityContext(new BasicSecurityConext(user, oldContext.isSecure()));
	}
}

Chi tiết về Filter các bạn xem lại bài viết “Filter và Interceptor với Jersey 2.x“.

Đăng ký RolesAllowedDynamicFeature

Để có thể áp dụng các Annotation @RolesAllowed, @PermitAll hoặc @DenyAll, chúng ta cần đăng ký sử dụng tính năng này với Jersey.


package com.gpcoder.config;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.logging.LoggingFeature;
//Deployment of a JAX-RS application using @ApplicationPath with Servlet 3.0
//Descriptor-less deployment
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;

public class JerseyServletContainerConfig extends ResourceConfig {
	public JerseyServletContainerConfig() {
		// if there are more than two packages then separate them with semicolon
		packages("com.gpcoder");
		register(new LoggingFeature(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME), Level.INFO,
				LoggingFeature.Verbosity.PAYLOAD_ANY, 10000));
		register(JacksonFeature.class);
		
		// This authorization feature is not automatically turned on.
		// We need to turn it on by ourself.
		register(RolesAllowedDynamicFeature.class);
	}
}

Tạo custom exception để xử lý kết quả trả về Client khi có ngoại lệ xảy ra

GenericExceptionMapper.java


package com.gpcoder.exception.mapper;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {

	@Override
	public Response toResponse(Throwable ex) {
		return Response.status(getStatusType(ex)) 
				.entity(ex.getMessage())
				.type(MediaType.TEXT_PLAIN) // "text/plain"
				.build();
	}
	
	private Response.StatusType getStatusType(Throwable ex) {
        if (ex instanceof WebApplicationException) {
            return((WebApplicationException)ex).getResponse().getStatusInfo();
        } else {
        	// 500, "Internal Server Error"
            return Response.Status.INTERNAL_SERVER_ERROR;
        }
    }
}

Chi tiết về xử lý ngoại lệ với Jersey, các bạn xem lại bài viết “HTTP Status Code và xử lý ngoại lệ RESTful web service với Jersey 2.x“.

Test ứng dụng với Postman

Test @GET http://localhost:8080/RestfulWebServiceExample/rest/orders/1

Như bạn thấy, chúng ta vẫn truy xuất được resource method với @PermitAll.

Test @DELETE http://localhost:8080/RestfulWebServiceExample/rest/orders/1

Chúng ta nhận được thông báo lỗi 403, do chưa được chứng thực với user có role Admin.

Bây giờ, chúng ta đăng nhập với user Admin để truy cập resource.

Chúng ta đã truy xuất thành công resource với user có role Admin.

Tương tự các bạn test với @POST, @PUT lần lượt với các username là customer, admin, test và password là gpcoder để xem kết quả.

Basic Authentication trong Jersey Client

Giới thiệu HttpAuthenticationFeature

Đối với Jersey Client, chúng ta có thể sử dụng HttpAuthenticationFeature để gửi thông tin chứng thực lên server.

HttpAuthenticationFeature hỗ trợ 4 mode chứng thực sau:

  • BASIC : client luôn gửi thông tin username và password trong mỗi request. Mode này cần phải kết hợp với SSL/TLS do nội dung username và password được mã hóa với Base64.
  • BASIC NON-PREEMPTIVE : thông tin chứng thực chỉ được gửi đi nếu server từ chối request và trả về status 401 và sau đó request được lặp lại với thông tin chứng thực được gửi đi.
  • DIGEST : sử dụng Http digest authentication. Thông tin dùng MD5 nhiều lần để mã hóa.
  • UNIVERSAL : kết hợp cả basic và digest authentication trong non-preemptive mode. Tức là trong trường hợp server response status 401, một authentication mode thích hợp được sử dụng dựa vào thông tin được yêu cầu từ phía server: WWW-Authenticate HTTP header.

Ví dụ tạo một HttpAuthenticationFeature


// Basic authentication mode
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("username", "password");

// Basic authentication – non-prempitive mode
feature = HttpAuthenticationFeature.basicBuilder()
		.nonPreemptive()
		.credentials("username", "password")
		.build();

// Digest mode
feature = HttpAuthenticationFeature.digest("user", "superSecretPassword");

// Universal mode
feature = HttpAuthenticationFeature.universal("user", "superSecretPassword");

// Building the feature in universal mode with different credentials for basic and digest
feature = HttpAuthenticationFeature.universalBuilder()
		.credentialsForBasic("username1", "password1") // will be used for basic authentication only
		.credentials("username2", "password2") // having different credentials for different schemes
		.build();

final Client client = ClientBuilder.newClient();
client.register(feature);

Tạo Jersey Client sử dụng Basic Authentication

Ví dụ: tạo ứng dụng REST Client truy cập @DELETE resource.


package com.gpcoder.client;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.glassfish.jersey.logging.LoggingFeature;

public class OrderServiceClient {

	public static final String API_URL = "http://localhost:8080/RestfulWebServiceExample/rest/orders";

	public static void main(String[] args) {
		// (1) Create client config
		ClientConfig clientConfig = new ClientConfig();

		// Config logging for client side
		clientConfig.register( //
				new LoggingFeature( //
						Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME), //
						Level.INFO, //
						LoggingFeature.Verbosity.PAYLOAD_ANY, //
						10000));

		// (2) Create basic authentication
		HttpAuthenticationFeature authDetails = HttpAuthenticationFeature.basic("admin", "gpcoder");

		// (3) Create jersey client with authentication
		Client client = ClientBuilder.newClient(clientConfig);
		client.register(authDetails);

		// (4) Call @DELETE API
		WebTarget target = client.target(API_URL).path("1");
		Invocation.Builder invocationBuilder = target.request(MediaType.APPLICATION_JSON_TYPE);
		final Response response = invocationBuilder.delete();

		// (5) Handle result
		System.out.println("Call delete() successful with the result: " + response.readEntity(String.class));
	}
}

Tài liệu tham khảo:

  • https://jersey.github.io/documentation/latest/filters-and-interceptors.html
  • https://jersey.github.io/documentation/latest/security.html
  • https://dennis-xlc.gitbooks.io/restful-java-with-jax-rs-2-0-2rd-edition/en/part1/chapter15/securing_jax_rs.html
4.3
06
Nếu bạn thấy hay thì hãy chia sẻ bài viết cho mọi người nhé! Và Donate tác giả

Shares

Chuyên mục: Java Webservice, REST Được gắn thẻ: Authentication, Interceptor, Jersey, REST, Webservice

Tìm hiểu về xác thực và phân quyền trong ứng dụng
Giới thiệu Json Web Token (JWT)

Có thể bạn muốn xem:

  • Làm thế nào để Test Jersey Rest API với JUnit? (22/08/2019)
  • REST Web service: JWT – Token-based Authentication trong Jersey 2.x (10/07/2019)
  • Giới thiệu Castle Mock – Mock REST APIs và SOAP web-services (05/09/2019)
  • Giới thiệu SOAP UI và thực hiện test Web Service (30/05/2019)
  • Tạo ứng dụng Java RESTful Client với thư viện OkHttp (15/07/2019)

Bình luận

bình luận

Tìm kiếm

Bài viết mới

  • Clean code 13/01/2024
  • Giới thiệu CloudAMQP – Một RabbitMQ server trên Cloud 02/10/2020
  • Kết nối RabbitMQ sử dụng Web STOMP Plugin 19/06/2020
  • Sử dụng publisher confirm trong RabbitMQ 16/06/2020
  • Sử dụng Dead Letter Exchange trong RabbitMQ 13/06/2020

Xem nhiều

  • Hướng dẫn Java Design Pattern – Factory Method (98122 lượt xem)
  • Hướng dẫn Java Design Pattern – Singleton (97739 lượt xem)
  • Giới thiệu Design Patterns (87887 lượt xem)
  • Lập trình đa luồng trong Java (Java Multi-threading) (86507 lượt xem)
  • Giới thiệu về Stream API trong Java 8 (83904 lượt xem)

Nội dung bài viết

  • 1 Giới thiệu Basic Authentication trong Jersey REST API
  • 2 Basic Authentication trong Jersey Server
  • 3 Basic Authentication trong Jersey Client

Lưu trữ

Thẻ đánh dấu

Annotation Authentication Basic Java Behavior Pattern Collection Creational Design Pattern Cấu trúc điều khiển Database Dependency Injection Design pattern Eclipse Exception Executor Service Google Guice Gson Hibernate How to Interceptor IO Jackson Java 8 Java Core JDBC JDK Jersey JMS JPA json JUnit JWT Message Queue Mockito Multithreading OOP PowerMockito RabbitMQ Reflection Report REST SOAP Structuaral Pattern Swagger Thread Pool Unit Test Webservice

Liên kết

  • Clean Code
  • JavaTpoint
  • Refactoring Guru
  • Source Making
  • TutorialsPoint
  • W3Schools Online Web Tutorials

Giới thiệu

GP Coder là trang web cá nhân, được thành lập với mục đích lưu trữ, chia sẽ kiến thức đã học và làm việc của tôi. Các bài viết trên trang này chủ yếu về ngôn ngữ Java và các công nghệ có liên quan đến Java như: Spring, JSF, Web Services, Unit Test, Hibernate, SQL, ...
Hi vọng góp được chút ít công sức cho sự phát triển cộng đồng Coder Việt.

Donate tác giả

Tìm kiếm các bài viết của GP Coder với Google Search

Liên hệ

Các bạn có thể liên hệ với tôi thông qua:
  • Trang liên hệ
  • Linkedin: gpcoder
  • Email: contact@gpcoder.com
  • Skype: ptgiang56it

Follow me

Copyright 2025 © GP Coder · All Rights Reserved · Giới thiệu · Chính sách · Điều khoản · Liên hệ ·

Share

Blogger
Delicious
Digg
Email
Facebook
Facebook messenger
Flipboard
Google
Hacker News
Line
LinkedIn
Mastodon
Mix
Odnoklassniki
PDF
Pinterest
Pocket
Print
Reddit
Renren
Short link
SMS
Skype
Telegram
Tumblr
Twitter
VKontakte
wechat
Weibo
WhatsApp
X
Xing
Yahoo! Mail

Copy short link

Copy link