Đề Xuất 12/2022 # Tạo Một Ứng Dụng Login Đơn Giản Và Bảo Mật Với Java Servlet Filter / 2023 # Top 18 Like | Jetstartakeontheworld.com

Đề Xuất 12/2022 # Tạo Một Ứng Dụng Login Đơn Giản Và Bảo Mật Với Java Servlet Filter / 2023 # Top 18 Like

Cập nhật nội dung chi tiết về Tạo Một Ứng Dụng Login Đơn Giản Và Bảo Mật Với Java Servlet Filter / 2023 mới nhất trên website Jetstartakeontheworld.com. Hy vọng thông tin trong bài viết sẽ đáp ứng được nhu cầu ngoài mong đợi của bạn, chúng tôi sẽ làm việc thường xuyên để cập nhật nội dung mới nhằm giúp bạn nhận được thông tin nhanh chóng và chính xác nhất.

Bảo mật (Security) là môt khía cạnh quan trọng của một ứng dụng có sự vận chuyển các dữ liệu quan trọng trên Internet.

Authentication (Xác thực)

Xác thực là quá trình mà quyền truy cập (access privileges) của người dùng được xác minh trước khi họ vào khu vực được bảo vệ của Website. Có hai cách tiếp cận xác thực chính: xác thực cơ bản và xác thực dựa trên biểu mẫu (Form-based authentication).

Basic Authentication (Xác thực cơ bản).

Với xác thực cơ bản, người dùng có thể truy cập mọi trang (page) hoàn toàn bình thường, với các trang yêu cầu bảo mật, một cửa sổ sẽ hiển thị để người dùng nhập vào username/password của họ. Thông tin username/password sẽ được gói lại gửi kèm theo yêu cầu (request) đến Server.

Khi người dùng nhập một đường dẫn trên trình duyệt, và nhấn Enter để yêu cầu một trang (page). Một thông tin “User Agent” được tạo ra và được gửi kèm theo yêu cầu. Thông thường thông tin này bao gồm thông tin trình duyệt của người dùng, thông tin hệ điều hành. Trong trường hợp xác thực cơ bản (basic authentication) thông tin username/password được gói bên trong “User Agent”.

Trong bài học này tôi không đề cập chi tiết về xác thực cơ bản.

Form-based Authentication (Xác thực dựa trên biểu mẫu)

Hầu hết các Website sử dụng hình thức xác thực dựa trên biểu mẫu (Form-based Authentication). Website cho phép người dùng truy cập mọi trang thông thường mà không yêu cầu mật khẩu. Tuy nhiên nếu người dùng truy cập vào một trang được bảo vệ, nó sẽ chuyển hướng tới một trang đăng nhập.

Trong bài học này, tôi sẽ đề cập chi tiết về cách sử dụng một Servlet Filter để bảo mật ứng dụng Java Web.

2- Khái niệm về Role và Principal

Trong bảo mật, có 2 khái niệm quan trọng là Principal và Role.

Role (Vai trò) là một tập hợp các quyền (permission) đối với một ứng dụng.

Để đơn giản tôi đưa ra một ví dụ, ứng dụng ABC có 2 vai trò “EMPLOYEE” (nhân viên) và “MANAGER” (Người quản lý).

Vai trò “EMPLOYEE” được phép sử dụng các chức năng bán hàng, và chức năng tạo mới thông tin khách hàng.

Vai trò “MANAGER” được phép sử dụng các chức năng quản lý nhân viên, và xem các báo cáo doanh thu.

Principal có thể tạm hiểu là một “Chủ thể” sau khi đã đăng nhập vào một hệ thống, họ có quyền làm điều gì đó trong hệ thống. Một “Chủ thể” có thể có một hoặc nhiều vai trò. Điều này phụ thuộc vào sự phân quyền của ứng dụng cho mỗi tài khoản người dùng khác nhau.

Trong ứng dụng Java Servlet, một Servlet Filter đặc biệt được sử dụng để xử lý bảo mật, nó thường được gọi là Security Filter.

Khi người dùng truy cập vào một trang (page) được bảo vệ, Security Filter sẽ kiểm tra, nếu người dùng chưa đăng nhập, yêu cầu của người dùng sẽ bị chuyển hướng (redirect) sang trang đăng nhập.

Nếu người dùng đã đăng nhập thành công, một đối tượng Principal được tạo ra, nó mang các thông tin của người dùng, bao gồm cả các vai trò.

Nếu người dùng đã đăng nhập thành công trước đó, và truy cập vào một trang (page) được bảo vệ. Security Filter sẽ kiểm tra các vai trò của người dùng có phù hợp để truy cập vào trang này hay không. Nếu không hợp lệ, nó sẽ hiển thị cho người dùng một trang thông báo truy cập bị cấm (Access Denied).

Đây là cấu trúc của ứng dụng mà chúng ta sẽ thực hiện:

Ứng dụng bao gồm 2 vai trò (Role) là EMPLOYEE và MANAGER.

Vai trò EMPLOYEE cho phép truy cập 2 trang /userInfo và /employeeTask

Vai trò MANAGER cho phép truy cập 2 trang /userInfo và /managerTask.

Tất cả các trang khác trong ứng dụng không yêu cầu phải đăng nhập.

Có 2 người dùng (user) là employee1 và manager1.

Người dùng employee1 được gán vai trò EMPLOYEE

Người dùng manager1 được gán cả 2 vai trò MANAGER và EMPLOYEE.

Người dùng có thể truy cập tất cả các trang không được bảo hộ một cách bình thường. Tuy nhiên nếu người dùng truy cập vào một trang ABC được bảo hộ, nó sẽ chuyển hướng (redirect) tới trang đăng nhập. Người dùng đăng nhập thành công, lúc này sẽ có 2 tình huống xẩy ra:

Ứng dụng sẽ chuyển hướng về trang ABC sau khi đăng nhập thành công, nếu userName có vai trò phù hợp.

Ứng dụng sẽ hiển thị thông báo truy cập bị từ chối (Access Denied) nếu userName có vai trò không phù hợp.

Lớp UserAccount đại diện cho người dùng của ứng dụng.

package org.o7planning.securitywebapp.bean; import java.util.ArrayList; import java.util.List; public class UserAccount { public static final String GENDER_MALE = "M"; public static final String GENDER_FEMALE = "F"; private String userName; private String gender; private String password; public UserAccount() { } public UserAccount(String userName, String password, String gender, String... roles) { this.userName = userName; this.password = password; this.gender = gender; if (roles != null) { for (String r : roles) { this.roles.add(r); } } } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } return roles; } this.roles = roles; } }

Lớp DataDAO được sử dụng để truy vấn dữ liệu trong Database (Mô phỏng).

package org.o7planning.securitywebapp.utils; import java.util.HashMap; import java.util.Map; import org.o7planning.securitywebapp.bean.UserAccount; import org.o7planning.securitywebapp.config.SecurityConfig; public class DataDAO { static { initUsers(); } private static void initUsers() { UserAccount emp = new UserAccount("employee1", "123", UserAccount.GENDER_MALE, SecurityConfig.ROLE_EMPLOYEE); UserAccount mng = new UserAccount("manager1", "123", UserAccount.GENDER_MALE, SecurityConfig.ROLE_EMPLOYEE, SecurityConfig.ROLE_MANAGER); mapUsers.put(emp.getUserName(), emp); mapUsers.put(mng.getUserName(), mng); } public static UserAccount findUser(String userName, String password) { UserAccount u = mapUsers.get(userName); if (u != null && u.getPassword().equals(password)) { return u; } return null; } }

7- SecurityConfig & SecurityUtils

Lớp SecurityConfig giúp cấu hình các vai trò và các chức năng (các trang) được phép truy cập ứng với vai trò đó.

package org.o7planning.securitywebapp.config; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; public class SecurityConfig { public static final String ROLE_MANAGER = "MANAGER"; public static final String ROLE_EMPLOYEE = "EMPLOYEE"; static { init(); } private static void init() { urlPatterns1.add("/userInfo"); urlPatterns1.add("/employeeTask"); mapConfig.put(ROLE_EMPLOYEE, urlPatterns1); urlPatterns2.add("/userInfo"); urlPatterns2.add("/managerTask"); mapConfig.put(ROLE_MANAGER, urlPatterns2); } return mapConfig.keySet(); } return mapConfig.get(role); } }

Lớp SecurityUtils là một lớp tiện ích, nó có các phương thức giúp kiểm tra một request (yêu cầu) có bắt buộc phải đăng nhập hay không, và request đó có phù hợp với vai trò của người dùng đã đăng nhập hay không.

package org.o7planning.securitywebapp.utils; import java.util.List; import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.o7planning.securitywebapp.config.SecurityConfig; public class SecurityUtils { public static boolean isSecurityPage(HttpServletRequest request) { String urlPattern = UrlPatternUtils.getUrlPattern(request); for (String role : roles) { if (urlPatterns != null && urlPatterns.contains(urlPattern)) { return true; } } return false; } public static boolean hasPermission(HttpServletRequest request) { String urlPattern = UrlPatternUtils.getUrlPattern(request); for (String role : allRoles) { if (!request.isUserInRole(role)) { continue; } if (urlPatterns != null && urlPatterns.contains(urlPattern)) { return true; } } return false; } } package org.o7planning.securitywebapp.utils; import java.util.Collection; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.ServletRegistration; import javax.servlet.http.HttpServletRequest; public class UrlPatternUtils { private static boolean hasUrlPattern(ServletContext servletContext, String urlPattern) { for (String servletName : map.keySet()) { ServletRegistration sr = map.get(servletName); if (mappings.contains(urlPattern)) { return true; } } return false; } public static String getUrlPattern(HttpServletRequest request) { ServletContext servletContext = request.getServletContext(); String servletPath = request.getServletPath(); String pathInfo = request.getPathInfo(); String urlPattern = null; if (pathInfo != null) { urlPattern = servletPath + "/*"; return urlPattern; } urlPattern = servletPath; boolean has = hasUrlPattern(servletContext, urlPattern); if (has) { return urlPattern; } int i = servletPath.lastIndexOf('.'); if (i != -1) { String ext = servletPath.substring(i + 1); urlPattern = "*." + ext; has = hasUrlPattern(servletContext, urlPattern); if (has) { return urlPattern; } } return "/"; } }

SecurityFilter là một Servlet Filter, nó làm nhiệm vụ kiểm tra các request trước khi cho phép truy cập vào các trang (page) được bảo hộ.

SecurityFilter đọc “các cấu hình bảo mật” đã được khai báo trong lớp SecurityConfig.

package org.o7planning.securitywebapp.filter; import java.io.IOException; import java.util.List; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.o7planning.securitywebapp.bean.UserAccount; import org.o7planning.securitywebapp.request.UserRoleRequestWrapper; import org.o7planning.securitywebapp.utils.AppUtils; import org.o7planning.securitywebapp.utils.SecurityUtils; @WebFilter("/*") public class SecurityFilter implements Filter { public SecurityFilter() { } @Override public void destroy() { } @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; String servletPath = request.getServletPath(); UserAccount loginedUser = AppUtils.getLoginedUser(request.getSession()); if (servletPath.equals("/login")) { chain.doFilter(request, response); return; } HttpServletRequest wrapRequest = request; if (loginedUser != null) { String userName = loginedUser.getUserName(); wrapRequest = new UserRoleRequestWrapper(userName, roles, request); } if (SecurityUtils.isSecurityPage(request)) { if (loginedUser == null) { String requestUri = request.getRequestURI(); int redirectId = AppUtils.storeRedirectAfterLoginUrl(request.getSession(), requestUri); response.sendRedirect(wrapRequest.getContextPath() + "/login?redirectId=" + redirectId); return; } boolean hasPermission = SecurityUtils.hasPermission(wrapRequest); if (!hasPermission) { RequestDispatcher dispatcher = request.getServletContext().getRequestDispatcher("/WEB-INF/views/accessDeniedView.jsp"); dispatcher.forward(request, response); return; } } chain.doFilter(wrapRequest, response); } @Override public void init(FilterConfig fConfig) throws ServletException { } }

UserRoleRequestWrapper.java

package org.o7planning.securitywebapp.request; import java.security.Principal; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * An extension for the HTTPServletRequest that overrides the getUserPrincipal() * and isUserInRole(). We supply these implementations here, where they are not * normally populated unless we are going through the facility provided by the * container. * If he user or roles are null on this wrapper, the parent request is consulted * to try to fetch what ever the container has set for us. This is intended to * be created and used by the UserRoleFilter. * * @author thein * */ public class UserRoleRequestWrapper extends HttpServletRequestWrapper { private String user; private HttpServletRequest realRequest; super(request); this.user = user; this.roles = roles; this.realRequest = request; } @Override public boolean isUserInRole(String role) { if (roles == null) { return this.realRequest.isUserInRole(role); } return roles.contains(role); } @Override public Principal getUserPrincipal() { if (this.user == null) { return realRequest.getUserPrincipal(); } return new Principal() { @Override public String getName() { return user; } }; } }

9- Trang chủ, Login, Logout

package org.o7planning.securitywebapp.servlet; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet({ "/", "/index" }) public class HomeServlet extends HttpServlet { private static final long serialVersionUID = 1L; public HomeServlet() { super(); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { RequestDispatcher dispatcher = this.getServletContext().getRequestDispatcher("/WEB-INF/views/homeView.jsp"); dispatcher.forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } package org.o7planning.securitywebapp.servlet; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.o7planning.securitywebapp.bean.UserAccount; import org.o7planning.securitywebapp.utils.AppUtils; import org.o7planning.securitywebapp.utils.DataDAO; @WebServlet("/login") public class LoginServlet extends HttpServlet { private static final long serialVersionUID = 1L; public LoginServlet() { super(); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { RequestDispatcher dispatcher = this.getServletContext().getRequestDispatcher("/WEB-INF/views/loginView.jsp"); dispatcher.forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String userName = request.getParameter("userName"); String password = request.getParameter("password"); UserAccount userAccount = DataDAO.findUser(userName, password); if (userAccount == null) { String errorMessage = "Invalid userName or Password"; request.setAttribute("errorMessage", errorMessage); RequestDispatcher dispatcher = this.getServletContext().getRequestDispatcher("/WEB-INF/views/loginView.jsp"); dispatcher.forward(request, response); return; } AppUtils.storeLoginedUser(request.getSession(), userAccount); int redirectId = -1; try { redirectId = Integer.parseInt(request.getParameter("redirectId")); } catch (Exception e) { } String requestUri = AppUtils.getRedirectAfterLoginUrl(request.getSession(), redirectId); if (requestUri != null) { response.sendRedirect(requestUri); } else { response.sendRedirect(request.getContextPath() + "/userInfo"); } } } package org.o7planning.securitywebapp.utils; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpSession; import org.o7planning.securitywebapp.bean.UserAccount; public class AppUtils { private static int REDIRECT_ID = 0; public static void storeLoginedUser(HttpSession session, UserAccount loginedUser) { session.setAttribute("loginedUser", loginedUser); } public static UserAccount getLoginedUser(HttpSession session) { UserAccount loginedUser = (UserAccount) session.getAttribute("loginedUser"); return loginedUser; } public static int storeRedirectAfterLoginUrl(HttpSession session, String requestUri) { Integer id = uri_id_map.get(requestUri); if (id == null) { id = REDIRECT_ID++; uri_id_map.put(requestUri, id); id_uri_map.put(id, requestUri); return id; } return id; } public static String getRedirectAfterLoginUrl(HttpSession session, int redirectId) { String url = id_uri_map.get(redirectId); if (url != null) { return url; } return null; } } package org.o7planning.securitywebapp.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/logout") public class LogoutServlet extends HttpServlet { private static final long serialVersionUID = 1L; public LogoutServlet() { super(); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getSession().invalidate(); response.sendRedirect(request.getContextPath() + "/"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } } package org.o7planning.securitywebapp.servlet; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/userInfo") public class UserInfoServlet extends HttpServlet { private static final long serialVersionUID = 1L; public UserInfoServlet() { super(); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { RequestDispatcher dispatcher = this.getServletContext().getRequestDispatcher("/WEB-INF/views/userInfoView.jsp"); dispatcher.forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" Employee Task || Manager Task || User Info || Login || Logout &nbsp;

/WEB-INF/views/homeView.jsp

/WEB-INF/views/loginView.jsp

manager1/123

/WEB-INF/views/userInfoView.jsp

Chạy ứng dụng:

package org.o7planning.securitywebapp.servlet; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/employeeTask") public class EmployeeTaskServlet extends HttpServlet { private static final long serialVersionUID = 1L; public EmployeeTaskServlet() { super(); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { RequestDispatcher dispatcher = this.getServletContext()// .getRequestDispatcher("/WEB-INF/views/employeeTaskView.jsp"); dispatcher.forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } package org.o7planning.securitywebapp.servlet; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/managerTask") public class ManagerTaskServlet extends HttpServlet { private static final long serialVersionUID = 1L; public ManagerTaskServlet() { super(); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { RequestDispatcher dispatcher = this.getServletContext()// .getRequestDispatcher("/WEB-INF/views/managerTaskView.jsp"); dispatcher.forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }

/WEB-INF/views/employeeTaskView.jsp

Hello, This is a protected page!

/WEB-INF/views/managerTaskView.jsp

Hello, This is a protected page!

/WEB-INF/views/accessDenied.jsp

Chạy ứng dụng:

Chạy ứng dụng và đăng nhập với userName = “employee1”, đây là người dùng có vai trò “EMPLOYEE”.

Chạy ứng dụng và đăng nhập với userName = “manager1”, đây là người dùng có 2 vai trò “EMPLOYEE” và “MANAGER”.

Hướng Dẫn Tạo Bản Quyền (License Product) Bảo Mật Ứng Dụng. / 2023

Hôm nay, mình xin hướng dẫn các bạn cách tạo License cho ứng dụng. Sau khi, tạo ứng dụng xong, nếu các bạn muốn phân phối ứng dụng của mình theo License product.

Ở bài viết này, mình hướng dẫn các bạn thuật toán cơ bản để bảo mật ứng dụng.

1. Đầu tiên, mình lấy serial number của HDD. Vì serial number HDD là duy nhất, nên ứng dụng mình cài vào máy tính nào thì chỉ sử dụng được một máy, cài đặt vào ổ cứng khác thì ứng dụng sẽ không hoạt động.

2. Tạo khóa token (chuỗi bảo mật).

3. Sử dụng thuật toán SHA1, serial number HDD với token của mình. Bạn có thể sử dụng thuật toán MD5, hay 1 thuật toán mã hóa bất kỳ.

Giao diện ứng dụng:

– Để xem Serial number HDD bạn có thể sử dụng câu lệnh MS-DOS

wmic diskdrive get serialnumber

– Import thư viện 

Imports System.ComponentModel Imports System.Text Imports System.Security.Cryptography Imports System.Management Imports System.Data Imports System.Data.SqlClient Public Function GetDriveSerialNumber() As String Dim hdd As New ManagementObjectSearcher("select * from Win32_DiskDrive") Dim hd As ManagementObject For Each hd In hdd.Get() HDD_Serial = hd("SerialNumber") Next Return HDD_Serial End Function

– Tiếp tục hàm mã hóa SHA1, 

Public Function SHA1(ByVal number As String) As String Dim ASCIIENC As New ASCIIEncoding Dim strreturn As String strreturn = vbNullString Dim bytesourcetxt() As Byte = ASCIIENC.GetBytes(number) Dim SHA1Hash As New SHA1CryptoServiceProvider Dim bytehash() As Byte = SHA1Hash.ComputeHash(bytesourcetxt) For Each b As Byte In bytehash strreturn &= b.ToString("X8") Next Return strreturn End Function

– Viết hàm tạo key license cho software

Dim key As String key = SHA1(serialHDD.Trim & token) chúng tôi = key End Sub

– Viết hàm đăng ký license cho ứng dụng

Dim keyLicense As String = txtRegister.Text Dim key As String key = SHA1(serialHDD.Trim & token) If (keyLicense = key) Then saveData(“update license set licensekey='” & keyLicense & “‘ where id=1”) Me.Hide() Dim frm As New XtraForm1 frm.ShowDialog() Else MessageBox.Show(“Key is not Valid!”, “ERROR!”) End If

End Sub

– Viết hàm kiểm tra khi chạy ứng dụng, ứng dụng đã được đăng ký hay chưa

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load chúng tôi = GetDriveSerialNumber().Trim() 'get serial key from data Dim keyData As String keyData = getData() 'get key from hdd serial with token sha1 Dim key As String key = SHA1(serialHDD.Trim & token) If keyData = key Then Me.Hide() Dim frm As New XtraForm1 frm.ShowDialog() End If End Sub

Cám ơn các bạn đã xem bài viết. Hãy like and share giúp mình nha các bạn.

Các bạn có thể Đăng ký kênh Youtube của mình ở góc phải bên trên màn hình.

Mọi thắc mắc về bài viết các bạn có thể hỏi ở http://hoidap.laptrinhvb.net để được support. 

Download source 

Tạo Một Email Html Đơn Giản, Có Tính Đáp Ứng / 2023

Trong bài này, tôi sẽ hướng dẫn cho bạn cách để tạo ra một email HTML có tính đáp ứng cao, đơn giản mà sẽ hoạt động trong mọi ứng dụng email, bao gồm tất cả các trình đọc email và ứng dụng trên thoại thông minh. Nó sử dụng Media Queries tối thiểu và một chiều rộng không cố định để đảm bảo khả năng tương thích tối đa.

Media Queries: Chỉ là một nửa giải pháp

Có những khi Media Queries là khá đủ để có được các email có tính đáp ứng, hoạt động trong iOS và các ứng dụng email trên Android, cả hai đều hỗ trợ Media Queries.

Kể từ đó, đã có một sự gia tăng của các ứng dụng email được tạo ra cho cả iOS và nền tảng Android, với các mức độ hỗ trợ khác nhau cho các phương pháp phát triển email có tính đáp ứng.

Đáng chú ý nhất là bản cập nhật mới nhất cho ứng dụng Gmail cho Android, được sử dụng nhiều trên Android ( chiếm 11% trong tổng số). Nó chưa bao giờ hỗ trợ Media Queries, và vẫn chưa hỗ trợ, tuy nhiên hiện nay nó giảm dung lượng các email của bạn xuống bằng cách nén kích thước của bảng bao quanh để vừa với khu vực có thể xem của màn hình. Thật khó để kiểm soát, và khi toàn bộ email của bạn dựa trên Media Queries, thì nó cho ra một số kết quả rất khó chịu.

Tại sao kích thước không cố định là giải pháp mới?

Tin tốt là bạn có thể thiết kế và xây dựng một email trông tuyệt vời ở mọi ứng dụng mail, bao gồm cả những cái không hỗ trợ Media Queries. Bạn có thể làm điều này bằng cách sử dụng một layout không cố định.

Tuy nhiên, có một vài nguyên tắt thiết kế mà bạn phải thực hiện. Những layout với các cột bằng nhau đó khi xếp chồng vào một cột không hoạt động tốt lắm khi sử dụng phương pháp này. Nếu bạn có thể học cách sống mà không có chúng, thì có một số thiết kế rất hiệu quả có thể làm việc rất tốt.

Bắt đầu

Được rồi, chúng ta hãy bắt đầu từ đầu.

body {margin: 0; padding: 0; min-width: 100%!important;} .content {width: 100%; max-width: 600px;} Hello!

Những gì chúng ta đang tạo ra ở đây là trang cơ bản với header, doctype và bảng container với một màu nền được áp dụng (cho cả body và một bảng lớn bao quanh, vì định phong cách cho thẻ body không được hỗ trợ đầy đủ). Để biết thêm thông tin về thiết lập cơ bản này, hãy xem Xây dựng một mẫu HTML Email từ đầu.

Sau đó, tôi thêm bảng nội dung chính của chúng ta vào giữa với tên lớp ‘content’.

Hãy chú ý

Lưu ý: Bạn sẽ thấy rằng trong hướng dẫn này tôi sẽ đặt CSS trong phần head của tài liệu. Tôi thường đặt phong cách trong head khi tôi có dự định tái sử dụng chúng, và đặt trực tiếp các phong cách chỉ sử dụng một lần.

Ẩn các phong cách Mobile khỏi Yahoo!

Bạn sẽ thấy rằng thẻ body có một thuộc tính phụ. Yahoo Mail rất thích biên dịch Media Queries của bạn, do đó, để ngăn chặn điều này, bạn cần sử dụng bộ chọn thuộc tính. Tôi tìm thấy cách dễ nhất để làm điều này (theo đề nghị của Email on Acid) là chỉ cần thêm một thuộc tính tùy ý vào thẻ body. Tôi nói là ‘yahoo’ nhưng nó có thể là bất cứ điều gì mà bạn thích:

Sau đó bạn có thể chọn các lớp cụ thể bằng cách sử dụng bộ chọn thuộc tính cho thẻ body của bạn trong CSS.

body[yahoo] .class {}

Hai thủ thuật cần để sử dụng tốt phương pháp chiều rộng không cố định

Như bạn có thể thấy, bảng ‘content’ của chúng ta được thiết lập độ rộng 100% để nó sẽ tự động chiếm toàn bộ chiều rộng của màn hình điện thoại và máy tính bảng. Chúng ta cũng sẽ thiết lập chiều rộng tối đa 600px để tránh email chiếm toàn bộ màn hình trên các thiết bị lớn hơn.

Hiện nay, có hai điều hơi khó khăn mà chúng ta cần phải giải quyết để đảm bảo rằng tất cả mọi thứ hiển thị như dự tính trên tất cả các trình đọc email. Hai cách khắc phục nhờ vào hướng dẫn tuyệt vời của Tina Ye cho phương pháp này có ở trên trang blog FogCreek.

1. Giải quyết vấn đề thiếu hỗ trợ Max-Width

Thật không may, max-width không được hỗ trợ bởi tất cả các trình đọc email. Để email của chúng ta để hiển thị tốt trong Outlook và Lotus Notes 8 & 8.5, chúng ta cần phải bao quanh mỗi bảng bằng một số code có điều kiện mà tạo ra một bảng cùng với một tập hợp chiều rộng để nắm giữ mọi thứ. Nó nhắm tới IE (đó là các công cụ kết xuất được sử dụng bởi Lotus Notes) và Microsoft Outlook.

Chúng tôi sẽ bao quanh bảng của chúng ta bằng một số code có điều kiện:

Hello!

2. Khắc phục cho Apple Mail

Thật là thú vị, Apple Mail (thường là một ứng dụng email rất tiến bộ) không hỗ trợ thuộc tính max-width. Mặc dù vậy nó hỗ trợ Media Queries, vì vậy chúng ta có thể thêm một cái để nói với nó thiết lập chiều rộng trên bảng ‘content’ của chúng ta, miễn là khung nhìn có thể hiển thị toàn bộ 600px.

@media only screen and (min-device-width: 601px) { .content {width: 600px !important;} }

Tạo Header

Bây giờ chúng ta sẽ thêm hàng đầu tiên của bảng-header. Thêm những thứ sau đây để định phong cách vào hàng mà chúng ta đã tạo ra:

Hello!

Và sau đó trong CSS của bạn, thêm padding cho header:

.header {padding: 40px 30px 20px 30px;}

Thêm hàng có tính đáp ứng đầu tiên

Bây giờ chúng ta sẽ tạo ra hàng đáp ứng đầu tiên của chúng ta. Cái cách mà chúng ta làm điều này là để tạo ra hai bảng ‘float’ nằm bên cạnh nhau bằng cách sử dụng thuộc tính ‘align’ của HTML.

Cột bên trái

Thay thế tiêu đề chào mừng “Hello!” bằng:

Chúng ttai tạo ra bảng 70px và đồng thời thêm một số padding mà sẽ hoạt động như là gutter giữa hai cột. Padding ở phía dưới sẽ thêm khoảng cách khi hai cột xếp chồng lên nhau trên màn hình điện thoại.

Cột bên phải

Chúng ta sẽ tạo ra cột bên phải bằng cách một lần nữa canh một bảng về bên trái. Bảng này sẽ rộng 445px, mà sẽ để lại 25px dư thừa ở phía bên tay phải. Điều này rất quan trọng vì Outlook sẽ tự động xếp chồng các bảng của bạn nếu không có ít nhất 25px dư thừa trên bất kỳ hàng nào.

Miễn là bạn cho phép trống ít nhất 25px, thì các bảng của bạn sẽ hiển thị như mong đợi.

Cho phép ít nhất 25px khoảng trắng để ngăn Outlook khỏi xếp chồng các bảng của bạn

Dừng lại! Mẹo khác để đối phó với Outlook

Để giải quyết điều này, bạn cũng có thể sử dụng code điều kiện để đánh lừa Outlook nghĩ rằng bạn đã đóng một ô và mở một cái ô mới. Sau thẻ đóng của bảng đầu tiên, và trước thẻ mở của bảng thứ hai, chỉ cần thêm:

Tiếp tục..

Ở trên cái rộng hơn, bên tay phải, chúng ta sẽ sử dụng phương pháp tương tự mà chúng ta sử dụng trên bảng container của chúng ta, bao gồm việc tạo ra một lớp và thêm code wrapper có điều kiện. Chúng ta muốn bảng này được rộng 100% trên điện thoại di động, nơi mà nó sẽ chuyển xuống bên dưới bảng bên trái.

Ở đây bạn có thể thấy rằng tôi đã tạo ra một lớp gọi là ‘col425’ cho bảng này, cột rộng 425px của chúng ta. Tôi đã quấn bảng trong code có điều kiện sẽ bao bọc nó trong một bảng rộng 425px. Sau đó chúng ta cũng thêm lớp của chúng ta vào Media Query mà chúng ta đã tạo cho Apple Mail.

.col425 {width: 425px!important;}

Bây giờ bên trong ô của chúng ta, chúng ta sẽ bổ sung thêm heading.

CREATING Responsive Email Magic

Tôi đã thêm một số lớp vào mỗi ô để tạo phong cách, cũng như một số phong cách mà tôi sẽ sử dụng cho các kiểu văn bản khác sau này:

.subhead {font-size: 15px; color: #ffffff; font-family: sans-serif; letter-spacing: 10px;} .h1 {font-size: 33px; line-height: 38px; font-weight: bold;} .h1, .h2, .bodycopy {color: #153643; font-family: sans-serif;}

Tạo hàng với một văn bản

Để tạo một dòng văn bản có một cột duy nhất, chúng ta chỉ cần thêm một hàng mới vào bảng ‘.content’ của chúng ta . Chúng ta sẽ thêm một lớp ‘innerpadding’ vào các hàng này với một số giá trị padding có thể tái sử dụng. Chúng ta cũng sẽ thêm một lớp ‘borderbottom’ để áp dụng đường viền vào mỗi hàng.

Welcome to our responsive email! Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tempus adipiscing felis, sit amet blandit ipsum volutpat sed. Morbi porttitor, eget accumsan dictum, nisi libero ultricies ipsum, in posuere mauris neque at erat.

CSS của chúng ta cho phần này:

.innerpadding {padding: 30px 30px 30px 30px;} .borderbottom {border-bottom: 1px solid #f2eeed;} .h2 {padding: 0 0 15px 0; font-size: 24px; line-height: 28px; font-weight: bold;} .bodycopy {font-size: 16px; line-height: 22px;}

Tạo Article hai cột

Bây giờ chúng ta sẽ tạo ra một hàng có tính đáp ứng giống như header của chúng ta, nhưng với kích thước hơi khác để chúng ta có thể có một hình ảnh lớn hơn.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tempus adipiscing felis, sit amet blandit ipsum volutpat sed. Morbi porttitor, eget accumsan dictum, nisi libero ultricies ipsum, in posuere mauris neque at erat.

Chúng ta đã thêm một button ở đây với lớp ‘buttonwrapper’. Nó chứa một ô có padding với một nền được thiết lập màu sắc, và sau đó một liên kết văn bản bên trong. Tôi thích sử dụng phương pháp này vì nó cho phép bạn có các button có chiều rộng không cố định, điều mà rất hữu ích khi tạo template có thể tái sử dụng, nơi chiều rộng của mỗi button sẽ khác nhau mỗi khi nó được sử dụng. Nếu bạn có một chiều rộng cố định cho các button, bạn có thể cần sử dụng Bulletproof Button Generator tại Campaign Monitor.

Các phong cách của chúng ta cho button:

.button {text-align: center; font-size: 18px; font-family: sans-serif; font-weight: bold; padding: 0 30px 0 30px;} .button a {color: #ffffff; text-decoration: none;}

Cũng như chiều rộng thiết lập của chúng ta cho lớp mới này ‘col380’, chúng ta sẽ thêm kích thước của chúng ta vào danh sách các phong cách để xử lý Apple Mail. Nó bây giờ trông như thế này:

@media only screen and (min-device-width: 601px) { .content {width: 600px !important;} .col425 {width: 425px!important;} .col380 {width: 380px!important;} }

Tạo hình ảnh một cột

Đây là một hàng rất đơn giản; chúng ta sẽ chỉ cần thiết lập một hình ảnh rộng 100% của email và chắc chắn rằng chiều cao của nó được thiết lập tự động sử dụng CSS.

Trong CSS của chúng ta:

img {height: auto;}

Tạo dòng văn bản cuối cùng

Cuối cùng chúng ta sẽ thêm một dòng văn bản mà không có đường viền ở phía dưới:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tempus adipiscing felis, sit amet blandit ipsum volutpat sed. Morbi porttitor, eget accumsan dictum, nisi libero ultricies ipsum, in posuere mauris neque at erat.

Tạo Footer

Để tạo footer, chúng ta sẽ thêm một hàng mới với một bảng bên trong. Một trong các hàng sẽ chứa một bảng khác cho các biểu tượng mạng xã hội của chúng ta.

Chúng ta sẽ thêm phong cách cần thiết cho footer của chúng ta với CSS:

.footer {padding: 20px 30px 15px 30px;} .footercopy {font-family: sans-serif; font-size: 14px; color: #ffffff;} .footercopy a {color: #ffffff; text-decoration: underline;}

Tối ưu hóa Button cho Mobile

Tôi sẽ sử dụng max-width và max-device-width trong Media Queries này để cố gắng tránh một lỗi trong chúng tôi trên IE9.

@media only screen and (max-width: 550px), screen and (max-device-width: 550px) { body[yahoo] .buttonwrapper {background-color: transparent!important;} body[yahoo] .button a {background-color: #e05443; padding: 15px 15px 13px!important; display: block!important;} }

Bây giờ, khi bạn nhấn bất cứ nơi nào trên nút được tô màu trên di động, thì đó là một liên kết!

Cải tiến hơn nữa Với Media Queries

Nếu bạn muốn, bây giờ bạn có thể bắt đầu thực hiện các cải tiến đối với layout của bạn bằng cách áp dụng các tên lớp vào các phần tử mà bạn muốn kiểm soát, và sau đó tạo ra các quy tắt mới trong Media Queries mà chúng ta vừa tạo ở trên.

Ví dụ, chúng ta sẽ lần lượt chuyển liên kết hủy bỏ đăng ký của chúng ta thành một button, bằng cách thêm một lớp vào liên kết:

và thêm CSS sau đây vào Media Queries:

body[yahoo] .unsubscribe {display: block; margin-top: 20px; padding: 10px 50px; background: #2f3942; border-radius: 5px; text-decoration: none!important; font-weight: bold;} body[yahoo] .hide {display: none!important;}

Bạn cũng có thể chọn lớp .innerpadding, .header và .footer để giảm padding trên các client mà hỗ trợ Media Queries.

Kiểm tra thử!

Cuối cùng, như mọi khi, hãy chắc chắn rằng bạn xác nhận (bằng cách sử dụng W3C validator – bạn sẽ chỉ có một lỗi cho thuộc tính ‘yahoo’ trên thẻ body) và kiểm tra thử trên các thiết bị thực và một dịch vụ trên web như Litmus hoặc Email on Acid.

Hãy thưởng thức việc tạo email có tính đáp ứng trông tuyệt vời ở mọi email client!

Để nâng cao những gì bạn đã học được lên cấp độ mới, hãy tìm hiểu các hướng dẫn sau:

Kiếm Tiền Online Đơn Giản Với Ứng Dụng Lozi Trên Smartphone / 2023

Lozi là gì?

Lozi là một trang mạng xã hội, một cộng đồng mua bán online. Bạn hãy thử truy cập vào website chúng tôi sẽ biết, ở đó bạn có thể tìm thấy những quán cà phê, quán chè, nhà hàng, tiệm bánh,…theo địa điểm cụ thể, có thể tìm thấy những món hàng yêu thích như mỹ phẩm, thời trang, sách truyện, xe cộ, thú cưng,…để mua. Không chỉ là người mua, bạn còn có thể đăng bán các sản phẩm của mình trên đó, cũng như quảng bá địa điểm quán ăn, quán uống….của bạn để tiếp cận đến nhiều người hơn. Còn nữa, bạn có thể chém gió, kết bạn trên đó. Chưa hết, còn điều thú vị nhất là bạn có thể kiếm tiền từ ứng dụng Lozi.

Làm sao để kiếm tiền từ Lozi?

Cũng như các trang mạng xã hội khác, do số lượng người dùng các ứng dụng smartphone ngày càng nhiều nên Lozi không dừng lại ở một website chúng tôi thông thường, mà còn được trang bị thêm ứng dụng dành cho các thiết bị di động như smartphone, máy tính bảng….Hiện tại ứng dụng Lozi đã có mặt trên 2 hệ điều hành Android và IOS.

Cách gì để kiếm tiền từ Lozi? Bán sản phẩm chăng? Vâng…bán sản phẩm thì tất nhiên bạn sẽ kiếm được tiền rồi. Nhưng ở đây, tôi không nói về mua bán, mà là muốn giới thiệu cho bạn biết ở Lozi bạn có thể kiếm tiền online miễn phí từ ứng dụng di động của nó, cụ thể là chia sẻ mã giới thiệu nhận thưởng hoa hồng.

Mỗi thành viên tại Lozi đều được cung cấp một mã giới thiệu riêng, công việc của bạn là chia sẻ ứng dụng Lozi và mã giới thiệu riêng của bạn cho bạn bè hoặc bất kỳ ai. Khi ai đó cài đặt ứng dụng Lozi và nhập mã giới thiệu của bạn thì cả bạn và người đó đều được thưởng ngay 10.000vnđ vào tài khoản. Tức là, có 10 người nhập mã giới thiệu của bạn thì bạn được 100.000vnđ, 100 người thì được 1000.000vnđ.

Đầu tiên bạn vào kho cửa hàng ứng dụng (App Store hoặc CH Play), tìm ứng dụng có tên “Lozi – Đăng và bán” rồi cài đặt vào máy của mình.

Mở ứng dụng Lozi sau khi đã cài xong, rồi bạn đăng nhập vào ứng dụng bằng tài khoản facebook hoặc số điện thoại.

Lưu ý: Bạn có thể không nhập mã giới thiệu cũng sẽ tiếp tục được nhưng sẽ không được nhận 10.000vnđ đầu tiên. Do vậy hãy nhập mã giới thiệu của tôi là GGOVP để cả tôi và bạn đều được nhận ngay 10.000vnđ từ Lozi.

Bạn cũng có thể nhấn vào “Chia sẻ mã” để chia sẻ ngay lên facebook hoặc các trang mạng xã hội khác.

Tóm lại nếu bạn là người đã tham gia Lozi, tức là đã có tài khoản tại Lozi thì bạn hãy đi mời những người chưa từng có tài khoản tại Lozi, mời họ cài ứng dụng Lozi và nhắc nhở họ nhập mã giới thiệu của bạn để nhận lấy 10.000vnđ cho cả bạn và họ.

Để kiểm tra xem số tiền bạn đang có, bạn vào theo đường dẫn: Biểu tượng cuối cùng (hình mặt nạ) ở chân màn hình ➔ “≡” (biểu tượng menu) ➔ Mời bạn bè, nhận thưởng.

0

0

votes

Article Rating

Bạn đang đọc nội dung bài viết Tạo Một Ứng Dụng Login Đơn Giản Và Bảo Mật Với Java Servlet Filter / 2023 trên website Jetstartakeontheworld.com. Hy vọng một phần nào đó những thông tin mà chúng tôi đã cung cấp là rất hữu ích với bạn. Nếu nội dung bài viết hay, ý nghĩa bạn hãy chia sẻ với bạn bè của mình và luôn theo dõi, ủng hộ chúng tôi để cập nhật những thông tin mới nhất. Chúc bạn một ngày tốt lành!