Integration of Shiro
Set up the environment
Building environments before integration is inevitable
Rely on
<dependencies>
<! - mysql driver - >
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<! --MyBatis starter-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<! -- Shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.0</version>
</dependency>
<! -- thymeleaf-spring-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<! -- thymeleaf-Java8-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
<! -- web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Copy the code
The database
You can write a simple database that holds the user login information.
Let's not write about roles and permissions
SpringBoot configuration file
In this configuration file, configure the connection information of the database and some necessary configurations of MyBatis
# Database connection information
spring:
datasource:
url: jdbc:mysql://localhost:3306/my_demo? useUnicode=true&useSSL=true&characterEncoding=utf-8
username: root
password: * * * * * * * * * * *
driver-class-name: com.mysql.cj.jdbc.Driver
# Turn off thymeleaf cache
thymeleaf:
cache: false
# MyBatis related
mybatis:
# mapper file path
mapper-locations: classpath:mybatis/mapper/*.xml
# alias
type-aliases-package: com.molu.pojo
Copy the code
Controller
Write a Controller Controller view jump
@Controller
public class MyController {
/ / home page
@RequestMapping({"/","index","index.html"})
public String toIndex(Model model){
model.addAttribute("msg"."Hello Shiro");
return "index";
}
/ / test page
@RequestMapping("/user/add")
public String toAdd(a){
return "/user/add";
}
@RequestMapping("/user/update")
public String toUpdate(a){
return "/user/update"; }}Copy the code
ShiroConfig
This profile associates Shiro’s three main classes
@Configuration
public class ShiroConfig {
// Create a Realm object that is responsible for retrieving login information from the database, meaning that authentication and authorization are handled by it
@Bean
public UserRealm getUserRealm(a){
return new UserRealm();
}
This object is the principal manager, which manages a number of principals and needs to inject our Realm into it for authentication and authorization
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getUserRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
/ / into the Realm
securityManager.setRealm(userRealm);
return securityManager;
}
// Create a filter factory object
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// Set the managed objects
bean.setSecurityManager(securityManager);
// Add Shiro built-in filters
LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
// "/user/add path access must be authenticated"
filterMap.put("/user/add"."authc");
bean.setFilterChainDefinitionMap(filterMap);
// Set the login page for unauthenticated login
bean.setLoginUrl("/toLogin");
returnbean; }}Copy the code
Realm
This class is responsible for our specific rules for authorization and certification.
Since authorization and authentication are not involved, they are left blank.
// Inherit this interface to override authentication and authorization methods
public class UserRealm extends AuthorizingRealm {
/ / authorization
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/ / certification
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
return null; }}Copy the code
Here the skeleton is built, nothing too difficult.
The simple HTML involved in view jumps is unnecessary.
We start the project
test
The filter
As you can see from the GIF above, both resource paths are freely accessible, authenticated or not.
To block access to a specific path, you can configure the filter chain in ShiroConfig.
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager securityManager){
// Get the filter factory instance
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// Set Shiro's core security interface
bean.setSecurityManager(securityManager);
// Pass custom filtering rules through map
LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
// "/user/add path access must be authenticated"
filterMap.put("/user/add"."authc");
bean.setFilterChainDefinitionMap(filterMap);
return bean;
}
Copy the code
If you are new to Shiro, you may be confused about the value passed into the map.
This “authC” is an instance of Shiro’s built-in filter
Commonly used Shiro filter instances
(1) Anon: anonymous filter, indicating that all resources that pass the URL configuration can be accessed
(2) AuthC: form-based filter, indicating that resources that pass the URL configuration need login authentication
(3) authcBasic: Basic authentication filter, indicating that resources that pass the URL configuration will prompt authentication
(4) PerMS: permission filter, which means that the access to resources that pass the URL configuration will check the corresponding permissions
(5) Port: port filter, which indicates the port number of the resource request that passes the URL configuration
(6) REST: Restful type filter: Restful check is performed on the resources that pass the URL configuration
(7) Roles: Role filter, which checks whether a url configured resource owns the role
(8) SSL: indicates an SSL filter, which indicates that the url configured resources can be accessed only through HTTPS
(9) User: user filter, indicating that login authentication/remember my way can be used to access resources that pass the URL configuration
(10) Logout: Exits the interceptor, indicating that the url configured resource can be jumped after the logout method is executed
As shown in the code above, we added an instance of “authc” filtering for the /user/add request, which means we can no longer access the path as a visitor (anonymous).
If you specify the same URL in different filter instances, Shiro will no longer match the same URL down through the filter instance.
At first glance, it sounds fine and reasonable, but some friends don’t pay attention to it and waste a lot of time.
Give me an example
filterMap.put("/user/**"."authc");
filterMap.put("/user/add"."anon");
Copy the code
As shown in the code above, I want to configure the interceptor instance “authc” for all paths starting with /user, but I want to put the “/user/add” path out again.
Let’s see if the “/user/add” path is accessible
No, although we have configured “anon” for it, it actually requires authentication to access it.
/user/** includes /user/add.
Now that it has been matched by the filter instance “authc” the “anon” instance is automatically invalidated.
So if you want to write wildcards in a path, it is recommended to write them in the background to avoid unnecessary trouble
As some eagle-eyed friends may have noticed, when we access a path that requires authentication as a visitor (anonymous), it redirects us to login.jsp and then 404.
This is setLoginUrl (); Method, which Shiro automatically calls when we are unauthenticated and access a path that requires authentication to access.
If we don’t set setLoginUrl(); By default, it automatically looks for the “/login.jsp” page or “/login” mapping in the root directory of the Web project.
We can also set it to a value that does not follow the default mapping.
/ / for setLoginUri (); Method to jump to this mapping when accessing an unauthorized path
bean.setLoginUrl("/toLogin");
Copy the code
Unlike SpringSecurity, Shiro does not write you a login page. The configured login page mapping must have the specified HTML file
/ / map
@RequestMapping("/toLogin")
public String toLogin(a){
return "login";
}
Copy the code
Accessing the update path that requires authentication at this point will lead you to the crude login page you just wrote.
certification
With the login page, you now need to perform some simple verification of the user login information from the database to complete the login authentication operation.
Fetching data should not need to be described too much, if the database is not able to forge some login information in memory.
Authentication and authorization are taken care of by the Realm class. If we access a path resource without authentication, we enter doGetAuthenticationInfo(). methods
The corresponding authentication logic is written in this method
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// Call the Service interface implementation class method to get the user login information in the database
ShiroUser user = userService.queryUser(userToken.getUsername()); // The implementation class method is to query the user by username
// If the user name passed in by the front-end login page does not match the user name in the database, the user information cannot be obtained
if (user==null) {// return null, which throws an exception
return null;
}
// We pass in three parameters to create the instance
// The second parameter is password. We can pass in the password to find the specified user from the database
// It compares the password passed in by the front end during authentication and throws an exception if it is inconsistent
return new SimpleAuthenticationInfo("",user.getPassword(),""); }}Copy the code
After data acquisition and verification, we also need to receive form data from the front end.
// The path must be the same as the form submission path
@PostMapping("/login")
public String login(String username, String password, Model model){
// Get the current user
Subject subject = SecurityUtils.getSubject();
// Encapsulates user login information into token instances
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
// Perform the login operation
subject.login(token);
// Forward to the home page
return "/index";
// Exception correlation
}catch (UnknownAccountException e){
// If the user name does not match the data in the database, carry MSG
model.addAttribute("msg"."Abnormal user name");
// Forward to the login page
return "/login";
/ / similar
}catch (IncorrectCredentialsException e){
model.addAttribute("msg"."Incorrect password");
return "/login"; }}Copy the code
Shiro is smart enough to throw an exception exactly if you write the logic right in your Realm class.
We use the root user in the database to complete the three operations of incorrect password, incorrect user name and successful login.
No problem, the most basic user authentication will do.
After authentication, you can access the resource path whose filter instance is “AuthC”.
authorization
Logged-in users can access different paths, which can also be done by configuring the Realm class.
Some examples of Shiro’s built-in filters were mentioned above. The perms-related one is “perms”, which checks the permissions the current user is carrying
As with “authC”, we can configure an instance of a “perms” filter for some paths so that access to the resource path requires associated permissions.
Write an admin.html and configure the filter instance "perms" for the mapping of the resource path
// The user must have the "user:admin" permission to access the resource path
filterMap.put("/user/admin"."perms[user:admin]");
Copy the code
Well, after writing this line of code, an authenticated user cannot access the resource if he or she does not carry the permission.
A 401 error will be reported when accessing, indicating no permission.
As well as setting up unauthenticated path jumps, we can also set up unauthorized path jumps through the ShiroFilterFactoryBean instance.
// Set unauthorized path jump
bean.setUnauthorizedUrl("/unauth");
Copy the code
The same thing, the HTML file and the Controller.
We can also write a logout request on a page that is not authorized to jump
// Unauthorized path mapping
@RequestMapping("/unauth")
public String Unauthorized(a){
// Redirect to the HTML file
return "/unauth";
}
// This path is mapped to the corresponding path in the unauth HTML file
@RequestMapping("/logout")
public String logout(a){
Subject subject = SecurityUtils.getSubject();
// Call the logout method
subject.logout();
// Log out and forward to the home page
return "index"; }}Copy the code
Grant permissions to users in doGetAuthorizationInfo(); Method.
Note that this method and doGetAuthenticationInfo(); It looks very similar. Pay attention to the distinction.
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// Get the authorization instance, which is the implementation class of the AuthorizationInfo interface
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// Invoke the authorization method from this instance to grant "user:admin" permission to all users who enter the method (doGetAuthorizationInfo)
info.addStringPermission("user:admin");
// return authorization instance
return info;
}
Copy the code
After these lines of code are written, all authenticated users will have access to the admin resource path only and will enter this method.
They will all be granted “user:admin” privileges, in other words, all of them will have administrator privileges.
This is obviously not possible, we should only give administrator privileges to certain users (root).
Regardless of how to do this, we will create a new column perms in the database and add the perms value “user:admin” to the root user.
After adding the column, remember to update the entity class.
Our user information is placed in doGetAuthenticationInfo(); Method, while the authorization is in doGetAuthorizationInfo(); Method.
So, is there a good way to do that when you’re checking, doGetAuthorizationInfo(); How to get user data?
Of course there is. In the previous password verification, we wrote this line of code.
SimpleAuthenticationInfo is the implementation class of the AuthenticationInfo interface
Creating this instance requires passing in three (or four) parameters, and we left the other two empty except for password.
Let’s look at the first parameter, principal, which can be a username or a User object.
// Pass the user object as the first argument
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
Copy the code
The user object encapsulates all the data corresponding to the username passed by token.
If we log in as root, the perms of root will also be encapsulated.
So all we need to do is go to doGetAuthorizationInfo(); Method takes the perms value encapsulated by the User object and grants it as permission.
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// Get the current user instance
Subject subject = SecurityUtils.getSubject();
// Call the method in the subject instance to get the user Object (strong, Object by default)
ShiroUser currentUser = (ShiroUser) subject.getPrincipal();
// Call the authorization method to get the perms value encapsulated in the User object
info.addStringPermission(currentUser.getPerms());
// return authorization instance
return info;
}
Copy the code
The database has only root usersperms
Columns can be fetched"user:admin"
In this way, only the root user can be granted the admin permission.
I have to say Shiro is very well designed
So that’s the end of it, there’s still a lot left to do
After all, SpringBoot is integrating Shiro, not Shiro, so I’ll write about it later