preface

This article mainly explains the following knowledge points:

  • Basic knowledge of rights management
    • model
    • Coarse-grained and fine-grained concepts
  • Review the implementation of URL interception
  • Introduction to Shiro and a simple introduction

First, Shiro basic knowledge

Before learning Shiro’s framework, let’s first understand the basics Shiro needs: permission management

1.1 What Is Rights Management?

As long as users participate in the system, permission management is generally implemented to control users’ access to the system. According to security rules or policies, users can access only authorized resources.

There are two main types of permission management:

  • User authentication
  • The user authorization

1.1.1 User Authentication

User authentication, the user to access the system, the system to verify the validity of the user identity

The e most common methods of user authentication: 1, user name and password, 2, fingerprint punch card machine, 3, certificate-based authentication method. The system verifies that a user is a valid user and can access system resources.

Here’s an example:

  • When we enter our taobao account and password, we can open the shopping cart

User authentication process:

  • Determine whether the resource can be accessed without authentication.
  • If the resource requires authentication to be accessed, determine whether the visitor is authenticated
  • If you are not already authenticated, you need to return to the login page for authentication
  • You can access resources only after being authenticated

There are several concepts we can extract from user authentication

  • Subject: a user, possibly a program, needs to access the resources of the system. The system needs to authenticate the subject
  • Principal identity information: Usually unique, a principal has multiple identities, but each has a primary principal (we can choose id, student id, etc.)
  • Credential information: can be a password, certificate, or fingerprint.

Conclusion: The subject needs to provide identity information and credential information during identity authentication.

1.1.2 User Authorization

User authorization is simply referred to as access control. After a user is authenticated, the system controls the user’s access to resources only when the user has the permission to access resources.

User authorization process

  • Reached the user authorization link, of course, is the need for user authentication
  • When a user accesses a resource, the system determines whether the user has the permission to operate the resource
  • If the user has the permission, the user cannot access it

The authorization process can be simply understood as follows: After the principal is authenticated, the system controls access

Subject must have access rights to the resource to access it..

Permission: Permission or permission for a resource. Subject has permission to access the resource. How to access/operate the resource requires defining permissions, such as user addition, user modification, and item deletion

Resources can be divided into two types

  • Resource type: The user information of the system is the resource type, which is equivalent to Java classes.
  • Resource instance: The user whose ID is 001 in the system is the resource instance, which is equivalent to the Java object new.

1.2 Permission Management Model

Generally, we can extract the following models:

  • Principal (account number, password)
  • Resources (resource name, access address)
  • Permission (Permission name, resource ID)
  • Role (Role name)
  • Role and permission relationship (Role ID, permission ID)
  • Principal and role relationships (principal ID, role ID)

Typically, enterprise development combines resource and permission tables into one permission table, as follows:

  • Resources (resource name, access address)
  • Permission (Permission name, resource ID)

Merger is:

  • Permission (Permission name, resource name, resource access address)

1.3 Assigning Rights

Users need to assign corresponding permissions to access corresponding resources. A permission is a permission to operate a resource.

Assigning resource permissions to users typically requires persisting permission information, such as storing it in a relational database. Write user information, permission management, and user-assigned permission information to the database (permission data model)

1.3.1 Role-based Access Control

Role Based Access Control (RBAC) : Role-based access control.


// If the user is the department manager, the code in if can be accessed
if(user.hasRole('Department Manager')) {// System resource content
	// View user reports
}
Copy the code

The role is divided for people. People, as users, belong to the active content in the system. If the resources that the role can access change, your code needs to be modified.


if(user.hasRole('Department Manager') || user.hasRole('General Manager')  ){
	// System resource content
	// View user reports
}
Copy the code

Role-based access control is not good for system maintenance (scalability is poor).

1.3.2 Resource-based Access Control

Resource Based Access Control (RBAC) : Resource based access control.

Resources are immutable in the system, such as methods in a class, buttons in a page.

Access to resources requires permission. The code can be written as follows:if(user.hasPermission ('User Report View (Permission Identifier)')) {// System resource content
	// View user reports
}
Copy the code

You are advised to use resource-based access control to implement permission management.


Coarse-grained and fine-grained permissions

Fine-grained permission management: Permission management for resource instances. The resource instance embodies the resource type, such as the modified connection for user 001, the user information for class 1110, and the employee in the administration department. Fine-grained rights management is data-level rights management.

Coarse-grained permission management For example, the super administrator can access all user add pages and user information pages. The department administrator can access the user information page including all buttons on the page.

Coarse-grained and fine-grained examples:

The system has a user list query page, the user list query divided permissions, if coarse particle management, Zhang SAN and Li Si have the user list query permissions, Zhang SAN and Li Si can access the user list query. For further fine particle management, Zhang SAN (Administration department) and Li Si (development department) can only query the user information of their own department. Zhang SAN can only view the user information of the administrative department, and Li Si can only view the user information of the development department. Fine-grained rights management is data-level rights management.Copy the code

2.1 How can I Implement coarse-grained Rights Management?

Coarse-grained permission management makes it easier to extract permission management code for unified processing at the system architecture level. For example: Authorization through the SpringMVC interceptor.

Fine-grained permission management is not common at the data level. Fine-grained permission management is a part of the system business logic and is relatively easy to handle at the business layer

For example, the department manager queries only the information about employees in the department and provides a parameter of the department ID on the Service interface. The controller obtains the department of the user based on the information about the current user. When invoking service, the department ID is passed into the Service so that the user can query only employees in the department.

2.1.1 Based on URL Interception

The method based on URL interception is commonly used in practical development.

For Web systems, URL interception is implemented through the Filter filter, and URL-based interception can also be implemented with the SpringMVC interceptor.

2.2.2 Using the Permission Management framework

For coarse-grained rights management, you are advised to use an excellent rights management framework to reduce development success and improve development efficiency.

Shiro is an excellent permission management framework.

Review URL blocking

We also used urls to block permissions several times along the way

At that time, we added and deleted permissions of the management system that should be searched, but resources were not added in the permission table, and we used Map collection instead. Blog.csdn.net/hon_3y/arti…

Later, we learned about dynamic proxies and annotations, and we also did an annotation-based interception

  • When the Controller gets the Service object, the Service factory returns a dynamic proxy object
  • The Controller calls the method with the proxy object, and the proxy object resolves whether there are annotations on the method
  • If there are annotations, then it is up to us to determine whether the subject is authenticated, and if so, whether the subject has permissions
  • Only when we have resolved that the principal’s permissions are consistent with our annotated permissions will we release!

Blog.csdn.net/hon_3y/arti…

Process:

3.1 Certified Javabeans

We used to put authentication on default Javabean objects. Now that we are going to learn Shiro, we need to be professional and create a Javabean that stores authentication information



Since Tomcat serializes sessions to local disks, use the Serializable interface **@author Thinkpad
 * 
 */
public class ActiveUser implements java.io.Serializable {
	private String userid;// User id (primary key)
	private String usercode;// User account
	private String username;// User name

	private List<SysPermission> menus;/ / the menu
	private List<SysPermission> permissions;/ / permission

	public String getUsername(a) {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}


	public String getUsercode(a) {
		return usercode;
	}

	public void setUsercode(String usercode) {
		this.usercode = usercode;
	}

	public String getUserid(a) {
		return userid;
	}

	public void setUserid(String userid) {
		this.userid = userid;
	}

	public List<SysPermission> getMenus(a) {
		return menus;
	}

	public void setMenus(List<SysPermission> menus) {
		this.menus = menus;
	}

	public List<SysPermission> getPermissions(a) {
		return permissions;
	}

	public void setPermissions(List<SysPermission> permissions) {
		this.permissions = permissions; }}Copy the code

Certified services


	@Override
	public ActiveUser authenticat(String userCode, String password)
			throws Exception {
		/** Authentication process: Query the database based on the user identity (account). If the user does not exist, compare the entered password with the database password. If the password is the same, the authentication succeeds */
		// Query the database based on the user account
		SysUser sysUser = this.findSysUserByUserCode(userCode);
		
		if(sysUser == null) {// Throw an exception
			throw new CustomException("User account does not exist");
		}
		
		// Database password (MD5 password)
		String password_db = sysUser.getPassword();
		
		// Compares the entered password with the database password. If the entered password is the same, the authentication succeeds
		// Use MD5 to encrypt the password entered on the page
		String password_input_md5 = new MD5().getMD5ofStr(password);
		if(! password_input_md5.equalsIgnoreCase(password_db)){// Throw an exception
			throw new CustomException("Wrong username or password");
		}
		// Get the user id
		String userid = sysUser.getId();
		// Query the menu by user ID
		List<SysPermission> menus =this.findMenuListByUserId(userid);
		
		// Query permission URL based on user ID
		List<SysPermission> permissions = this.findPermissionListByUserId(userid);
		
		// If the authentication succeeds, the user identity information is returned
		ActiveUser activeUser = new ActiveUser();
		activeUser.setUserid(sysUser.getId());
		activeUser.setUsercode(userCode);
		activeUser.setUsername(sysUser.getUsername());// User name
		
		// Put the menu and URL of the permission scope
		activeUser.setMenus(menus);
		activeUser.setPermissions(permissions);
		
		return activeUser;
	}
Copy the code

The Controller handles authentication and, if successful, stores the authentication information in the Session

	@RequestMapping("/login")
	public String login(HttpSession session, String randomcode,String usercode,String password)throws Exception{
		// Verify the verification code to prevent malicious attacks
		// Get the correct verification code from session
		String validateCode = (String) session.getAttribute("validateCode");
		
		// Compare the input validation with the session validation
		if(! randomcode.equals(validateCode)){// Throw an exception
			throw new CustomException("Verification code input error");
		}
		
		// Invoke service to verify the user account and password
		ActiveUser activeUser = sysService.authenticat(usercode, password);
		
		// If the service passes the verification, the user's identity is recorded to the session
		session.setAttribute("activeUser", activeUser);
		// Redirect to the product query page
		return "redirect:/first.action";
	}
	
Copy the code

Authentication interceptor



	// this is executed before the handler is executed
	// Used for user authentication and permission verification
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		
		// Get the requested URL
		String url = request.getRequestURI();
		// Check if it is a public address
		// The actual development requires the public address to be configured in the configuration file
		// Access the URL with the inverse name from the configuration
		List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL");
		// Pass through the public address
		for(String open_url:open_urls){
			if(url.indexOf(open_url)>=0) {// If it is a public address, it is allowed
				return true; }}// Determine whether the user identity exists in the session
		HttpSession session = request.getSession();
		ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
		// If the user identity is allowed in the session
		if(activeUser! =null) {return true;
		}
		// Execute the intercept here, jump to the login page, user authentication
		request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
		
		// If false is returned, handler execution will not continue; if true, handler execution will be allowed
		return false;
	}
Copy the code

Authorized interceptor


	// this is executed before the handler is executed
	// Used for user authentication and permission verification
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		
		// Get the requested URL
		String url = request.getRequestURI();
		// Check if it is a public address
		// The actual development requires the public address to be configured in the configuration file
		// Access the URL with the inverse name from the configuration
		
		List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL");
		// Pass through the public address
		for(String open_url:open_urls){
			if(url.indexOf(open_url)>=0) {// If it is a public address, it is allowed
				return true; }}// Get the public access address from the configuration file
		List<String> common_urls = ResourcesUtil.gekeyList("commonURL");
		// Traverses the public address. If it is a public address, pass through
		for(String common_url:common_urls){
			if(url.indexOf(common_url)>=0) {// If it is a public address, it is allowed
				return true; }}/ / get the session
		HttpSession session = request.getSession();
		ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
		// Get the scope url from session
		List<SysPermission> permissions = activeUser.getPermissions();
		for(SysPermission sysPermission:permissions){
			// Permission url
			String permission_url = sysPermission.getUrl();
			if(url.indexOf(permission_url)>=0) {// If it is a permission URL, the permission is allowed
				return true; }}// Perform the interception here, jump to the unauthorized prompt page
		request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(request, response);
		
		// If false is returned, handler execution will not continue; if true, handler execution will be allowed
		return false;
	}
Copy the code

Interceptor configuration:



	<! -- Interceptor -->
	<mvc:interceptors>

		<mvc:interceptor>
			<! -- User authentication interception -->
			<mvc:mapping path="/ * *" />
			<bean class="cn.itcast.ssm.controller.interceptor.LoginInterceptor"></bean>
		</mvc:interceptor>
		<mvc:interceptor>
			<! -- Authorized interception -->
			<mvc:mapping path="/ * *" />
			<bean class="cn.itcast.ssm.controller.interceptor.PermissionInterceptor"></bean>
		</mvc:interceptor>
	</mvc:interceptors>

Copy the code

What is Shiro

Shiro is an open source framework of Apache. It is a permission management framework to implement user authentication and user authorization.

Spring includes Spring Security (formerly Acegi), a permissions framework that relies too closely on Spring and is not as easy to use as Shiro. Shiro does not depend on Spring, shiro can not only realize the permission management of Web applications, but also realize the permission management of C/S systems and distributed systems. Shiro is a lightweight framework, and more and more enterprise projects begin to use Shiro.

Shiro architecture:

  • Subject: the subject can be a user or a program. To access the system, the system needs to authenticate and authorize the subject.
  • SecurityManager: securityManager through which a principal authenticates and authorizes itself.
  • Authenticator: authenticator. The principal authenticates through the authenticator.
  • Authorizer: Authorizer, the subject’s authorization is ultimately done through the authorizer.
  • SessionManager: Generally, Web applications use web containers to manage sessions. Shiro also provides a set of session management methods.
  • SessionDao: Manages session data through the SessionDao. The SessionDao is used for personalized session data storage.
  • Cache Manager: a cacheManager that caches session and authorization data. For example, cacheManager caches authorization data, and EhCache integrates it to manage cached data.
  • Realm: a data source that authenticates and authorizes data through a realm.

Cryptography: Password management provides a set of encryption/decryption components for easy development. For example, it provides common functions such as hashing, encryption and decryption.

  • Like the MD5 hash algorithm.

Why Shiro

When we use URL interception, we need to configure all urls, which is tedious and difficult to maintain

Shiro implements the system’s authority management, effectively improving the development efficiency, thereby reducing the development cost.

Shiro certification

6.1 Importing jar Packages

We’ll just use Maven’s coordinates


	<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>1.2.3</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-web</artifactId>
			<version>1.2.3</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.2.3</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-ehcache</artifactId>
			<version>1.2.3</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-quartz</artifactId>
			<version>1.2.3</version>
		</dependency>
Copy the code

Of course, we can also import Shiro related JAR packages into it


<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-all</artifactId>
	<version>1.2.3</version>
</dependency>


Copy the code

6.2Shiro Certification Process

6.2.1 Creating a factory using a Configuration file


	// The user logs in and logs out
	@Test
	public void testLoginAndLogout(a) {

		// Create a securityManager factory. Create a securityManager factory from the INI configuration file
		Factory<SecurityManager> factory = new IniSecurityManagerFactory(
				"classpath:shiro-first.ini");

		/ / create the SecurityManager
		SecurityManager securityManager = factory.getInstance();

		// Set the securityManager in the current running environment
		SecurityUtils.setSecurityManager(securityManager);

		Create a subject from SecurityUtils
		Subject subject = SecurityUtils.getSubject();

		// Prepare tokens before authentication submission
		// The account and password will be entered by the user
		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan"."111111");
		try {
			// Perform the authentication commit
			subject.login(token);
		} catch (AuthenticationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// Whether the authentication succeeds
		boolean isAuthenticated = subject.isAuthenticated();

		System.out.println("Certified or not:" + isAuthenticated);

		// Exit the operation
		subject.logout();

		// Whether the authentication succeeds
		isAuthenticated = subject.isAuthenticated();

		System.out.println("Certified or not:" + isAuthenticated);

	}
Copy the code

6.3 summary

ModularRealmAuthenticator authentication, you need to call realm query user information (user information) is found in the database ModularRealmAuthenticator password comparison (certification process). Realm: The database needs to be queried based on the identity information contained in the token (the ini configuration file is used for starters). Return user authentication if found, or null if not found.

6.4 Customizing a Realm

As you can see from the first authenticator, the process we’re talking about is the authenticator going to a Realm and looking for our data. By default, realms are checked directly against configuration files, and we typically use realms to be checked against databases during development. Therefore, we need to customize the realm


public class CustomRealm extends AuthorizingRealm {

	// Set the name of the realm
	@Override
	public void setName(String name) {
		super.setName("customRealm");
	}

	// Used for authentication
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException {

		// The token is entered by the user
		// Retrieve the identity information from the token
		String userCode = (String) token.getPrincipal();

		// Step 2: query the database according to the userCode entered by the user
		/ /...
	

		// Return null if no query is found
		// The username in the database is zhangsansan
		/*if(! userCode.equals("zhangsansan")){// return null; } * /
		
		
		// Query the password from the database
		String password = "111112";

		// If found, the authentication information AuthenticationInfo is returned

		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
				userCode, password, this.getName());

		return simpleAuthenticationInfo;
	}

	// Used for authorization
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null; }}Copy the code

6.5 configuration realm

You need to configure realm injection into securityManager in shro-realm. ini.

6.6 Testing a Custom Realm

As with the starter program above, you need to change the INI configuration file path:

As with the previous starter, you need to change the ini configuration file path: Factory<SecurityManager> Factory =new IniSecurityManagerFactory(
				"classpath:shiro-realm.ini");
Copy the code

6.7 Hash Algorithm

If we know MD5, we know that MD5 is irreversible, but if we set some low security password: 111111… Even if it is irreversible, you can still use brute force algorithm to get the plaintext corresponding to MD5…

It is recommended to hash MD5 with salt (salt). Encryption is equivalent to hashing the original password + salt. \

Hash method in normal use:

  • Hash the original password + salt in the program, store the hash value in the database, and also store the salt in the database.

Testing:


public class MD5Test {
	
	public static void main(String[] args) {
		
		// The original password
		String source = "111111";
		/ / salt
		String salt = "qwerty";
		// Hash times
		int hashIterations = 2;
		/ / hash above 1 times: f3694f162729b7d0254c6e40260bf15c
		/ / hash above 2 times: 36 f2dfa24d0a9fa97276abbe13e596fc
		
		
		// Select * from ();
		// First argument: plain text, original password
		// The second argument: salt, by using a random number
		// The number of hashes, such as two hashes, is equivalent to MD5 (MD5 ('')).
		Md5Hash md5Hash = new Md5Hash(source, salt, hashIterations);
		
		String password_md5 =  md5Hash.toString();
		System.out.println(password_md5);
		// First argument: hash algorithm
		SimpleHash simpleHash = new SimpleHash("md5", source, salt, hashIterations); System.out.println(simpleHash.toString()); }}Copy the code

6.8 Custom Realm Supports MD5

Custom realm


public class CustomRealmMd5 extends AuthorizingRealm {

	// Set the name of the realm
	@Override
	public void setName(String name) {
		super.setName("customRealmMd5");
	}

	// Used for authentication
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException {

		// The token is entered by the user
		// Retrieve the identity information from the token
		String userCode = (String) token.getPrincipal();

		// Step 2: query the database according to the userCode entered by the user
		/ /...

		// Return null if no query is found
		// The username in the database is zhangsansan
		/* * if(! userCode.equals("zhangsansan")){// return null; } * /

		// Simulate query from database to password, hash value
		String password = "f3694f162729b7d0254c6e40260bf15c";
		// Get the salt from the database
		String salt = "qwerty";
		// The hash value above corresponds to the salt in plain text: 111111

		// If found, the authentication information AuthenticationInfo is returned
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
				userCode, password, ByteSource.Util.bytes(salt), this.getName());

		return simpleAuthenticationInfo;
	}

	// Used for authorization
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null; }}Copy the code

Configuration file:

Testing:


// Create a custom realm for hash matching
	@Test
	public void testCustomRealmMd5(a) {

		// Create a securityManager factory. Create a securityManager factory from the INI configuration file
		Factory<SecurityManager> factory = new IniSecurityManagerFactory(
				"classpath:shiro-realm-md5.ini");

		/ / create the SecurityManager
		SecurityManager securityManager = factory.getInstance();

		// Set the securityManager in the current running environment
		SecurityUtils.setSecurityManager(securityManager);

		Create a subject from SecurityUtils
		Subject subject = SecurityUtils.getSubject();

		// Prepare tokens before authentication submission
		// The account and password will be entered by the user
		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan"."222222");

		try {
			// Perform the authentication commit
			subject.login(token);
		} catch (AuthenticationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// Whether the authentication succeeds
		boolean isAuthenticated = subject.isAuthenticated();

		System.out.println("Certified or not:" + isAuthenticated);

	}
Copy the code

Seven,

  • User authentication and user authorization are the foundation of Shiro. User authentication is essentially login and user authorization is essentially resource interception.
  • The model of permission management generally manages resources in a permission table.
  • We can intercept based on role or resource. With role-based interception, if the role’s permissions change, the code needs to be modified **. Resource-based interception is recommended
  • For this URL interception, we use a JavaBean to encapsulate all authentication information. When the user logs in, we encapsulate the user’s access to the menu bar and resources into the JavaBean
  • When using the interceptor for user authentication, we simply determine whether the Session field has JavaBen objects.
  • When the interceptor authorizes the user, we need to determine whether the permissions in the JavaBean can access the resource.
  • The previous METHOD of URL interception requires that all urls are managed in the database. Very cumbersome and difficult to maintain.
  • We want Shiro to use realm to query the database for authentication. Our reAML queries configuration file data by default.
  • Therefore, we need to customize reAML so that it goes to the database to query the data. Simply inherit the AuthorizingRealm class.
  • Of course, the custom reAML also needs to write the location of our custom REAML in the configuration file.
  • Hashing algorithms are designed to keep passwords from being cracked. The original code can be hashed with salt, which makes it harder to crack.
  • Custom reAML also supports hashing algorithm, again, we need to configure it in the configuration file.

If the article has the wrong place welcome to correct, everybody exchanges with each other. Students who are used to reading technical articles on wechat and want to get more Java resources can follow the wechat public account :Java3y