I heard that if you search the public account “Java Fish boy” on wechat, you will improve your skills to the next level
(1) Overview
Shiro is a security framework of Apache, Shiro can easily develop a good enough security application, Shiro can complete authentication, authorization, encryption, session management, caching and other functions. Looking at Shiro from an application point of view, we can see that Shiro runs mainly as follows:
The object with which the application code interacts directly is Subject, which means that Shiro’s external API core is Subject. The meaning of each API:
Subject: represents the current “user”, who is not necessarily a specific person. Anything that interacts with the current application is Subject, such as web crawler, robot, etc. An abstract concept; All subjects are bound to the SecurityManager, and all interactions with the Subject are delegated to the SecurityManager; Subject can be thought of as a facade; The SecurityManager is the actual enforcer;
SecurityManager: SecurityManager. That is, all security-related operations interact with the SecurityManager; And it manages all subjects; As you can see, it is the core of Shiro and is responsible for interacting with other components described later. If you have studied SpringMVC, you can think of it as the DispatcherServlet front-end controller;
Realm: Shiro obtains security data from realms (users, roles, and permissions). To authenticate users, The SecurityManager needs to obtain the corresponding users from realms for comparison. You also need to get the user’s role/permissions from Realm to verify that the user can operate. You can think of a Realm as a DataSource.
(2) SpringBoot integration Shiro
First, introduce Shiro dependencies
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
Copy the code
With these concepts in mind, we can start writing code that uses Shiro in just three steps
1. Create a Realm object
2. Create a security manager and bind realm objects
3. Create Shiro filter factory and bind security manager
The first step is to customize a Realm object
public class UserRealm extends AuthorizingRealm {
/ / authorization
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/ / certification
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null; }}Copy the code
Define a UserRealm that inherits AuthorizingRealm and implements interface methods that represent authorization and authentication, much like SpringSecurity.
Next, create a ShiroConfig class to implement Shiro’s configuration
@Configuration
public class ShiroConfig {
// Create a Realm object
@Bean(name = "userRealm")
public UserRealm userRealm(a){
return new UserRealm();
}
// Create security manager
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
// Bind realm objects
securityManager.setRealm(userRealm());
return securityManager;
}
// Create Shiro filter factory
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
// Set the security manager
bean.setSecurityManager(defaultWebSecurityManager);
returnbean; }}Copy the code
(iii) Shiro realizes landing interception
To implement login blocking in Shiro, you only need to configure the filter in Shiro’s filter factory. For more detailed display, we need to create four NEW HTML pages: index, Level1, Level2, and login
Additionally, thymeleaf and spring-boot-starter-Web dependencies need to be introduced
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
Copy the code
Index.html:
<! DOCTYPEhtml>
<html xmlns:th="http://www.thymeleaf.org"><! - introduce thymeleaf -- -- >
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Home page</h1>
<h2 th:text="${msg}"></h2>
<a th:href="@{/level1}">level1</a>
<a th:href="@{/level2}">level2</a>
</body>
</html>
Copy the code
login.html
<! DOCTYPEhtml>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Landing page</title>
</head>
<body>
<div>
<p th:text="${errormsg}"></p>
<form action="/checklogin" method="post">
<h2>Landing page</h2>
<input type="text" id="username" name="username" placeholder="username">
<input type="password" id="password" name="password" placeholder="password">
<button type="submit">landing</button>
</form>
</div>
</body>
</html>
Copy the code
Level1, level2 stores
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
level1
</body>
</html>
Copy the code
Then write the indexController
@Controller
public class indexController {
@RequestMapping({"/","/index"})
public String index(Model model){
model.addAttribute("msg"."hello,shiro");
return "index";
}
@RequestMapping("/level1")
public String level1(Model model){
return "level1";
}
@RequestMapping("/level2")
public String level2(Model model){
return "level2";
}
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String tologin(a){
return "login"; }}Copy the code
With this preliminary work done, we can configure the filter interceptor by adding the required filters to the ShiroFilterFactoryBean method in the ShiroConfig class. Shiro has five built-in filters
/** * anon: access without authentication * authc: access without authentication * user: access with remember me function * perms: access with permission on a resource * role: access with permission on a role */
Copy the code
In use, just assign different filters to the corresponding pages
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
// Set the security manager
bean.setSecurityManager(defaultWebSecurityManager);
Add shiro's built-in filters
Map<String,String> filterMap=new LinkedHashMap<>();
filterMap.put("/index"."anon");
filterMap.put("/level1"."authc");
filterMap.put("/level2"."authc");
bean.setFilterChainDefinitionMap(filterMap);
// Set the jump to the login page
bean.setLoginUrl("/login");
return bean;
}
Copy the code
In this code, we set the index home page to be accessible without permission, level1 and level2 to be authenticated, and the login page to jump to the login page. Click level1 and Level2 to check whether there is any authentication. If there is no authentication, the login page is automatically redirected.
(4) Shiro realizes user authentication
Shiro’s user authentication is done in realm. First, we need to change the controller login logic. We need to encapsulate the username and password passed by the user into Shiro and write a new method to determine whether the user is logged in
@RequestMapping(value = "/checklogin",method = RequestMethod.POST)
public String login(@RequestParam("username") String username,@RequestParam("password") String password,Model model){
// Get the current user
Subject subject = SecurityUtils.getSubject();
// Store error information
String msg="";
// If not authenticated
if(! subject.isAuthenticated()){// Encapsulate the username and password in Shiro
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
// Execute the login method
subject.login(token);
}catch (UnknownAccountException e){// The user name does not exist
msg=e.getMessage();
}catch (IncorrectCredentialsException e){// The password is incorrect
msg=e.getMessage();
}catch (Exception e){
msg="User login exception";
e.printStackTrace();
}
// If MSG is empty, there is no exception
if (msg.isEmpty()){
return "redirect:/index";
}else {
model.addAttribute("errormsg",msg);
return "login"; }}return "login";
}
Copy the code
In this login controller, we first judge whether the user exists through the subject, and if not, encapsulate the user data for login. The login action needs to be executed in a Realm. Back to UserRealm’s authentication method:
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// The token is obtained
UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
// Get the user name and password from the token
String username = token.getUsername();
String password = String.valueOf(token.getPassword());
// For convenience, the user is not fetched from the database
if (!"root".equals(username)) {
throw new UnknownAccountException("User does not exist");
}else if (!"123456".equals(password)){
throw new IncorrectCredentialsException("Password error");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,"123456",getName());
return info;
}
Copy the code
In the previous code, we stored the username and password in UsernamePasswordToken, which can be obtained from UserRealm using authenticationToken. After obtaining the username and password, we can compare the username and password with the database. And raises different exceptions, which are picked up by LLDB etMessage().
The important thing to notice here is that the return value SimpleAuthenticationInfo. This class is the implementation class of AuthenticationInfo. The constructor of SimpleAuthenticationInfo takes three arguments:
The first argument is principal, which is usually passed in the user name or user entity class, and then gets the current logged-in user from somewhere else using this code
SecurityUtils.getSubject().getPrincipal();
Copy the code
The second parameter is the password. Note that this refers to the password in the database, since we have already done a layer of password judgment in the previous code, the password verification here is not very effective.
The third argument is the name of the Realm, which can be obtained directly using the getName() method.
(4.1) Effect display
After entering the home page, click Level1 or Level2 to automatically jump to the landing page
On the login page, if the user name is entered incorrectly, the user does not exist. If the password is incorrect, the password is displayed
When the user name and password are entered correctly, click Level1 or Level2 to enter the home page.
(V) Shiro realizes user authorization
In the front we have achieved user authentication, next we will do user authorization, user authorization can be seen in many scenarios, such as some pages of a website only VIP can see.
We to simulate this scenario, the first change ShiroConfiggetShiroFilterFactoryBean method, increase the permissions
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
// Set the security manager
bean.setSecurityManager(defaultWebSecurityManager);
Add shiro's built-in filters
Map<String,String> filterMap=new LinkedHashMap<>();
// Level1 is accessible only by vip1
filterMap.put("/level1"."perms[vip1]");
filterMap.put("/index"."anon");
bean.setFilterChainDefinitionMap(filterMap);
bean.setLoginUrl("/login");
// If you do not have permission to jump to the page
bean.setUnauthorizedUrl("/unauthorizedUrl");
return bean;
}
Copy the code
Level1: /unauthorizedUrl = /unauthorizedUrl = /unauthorizedUrl = /unauthorizedUrl = /unauthorizedUrl
@RequestMapping("/unauthorizedUrl")
@ResponseBody
public String unAuthorizedUrl(a){
return "Current user does not have access";
}
Copy the code
Then code the authorization part in UserRealm:
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// Add vip1 permissions here for each user
info.addStringPermission("vip1");
return info;
}
Copy the code
In this code, add vip1 permission to all users by addStringPermission. In a real environment, we would give different permissions to different users. For example, add a permission field in the database, and replace vip1 in the above code with the database permission field.
(6) Summary
So far, we have covered shiro’s introduction, basic usage, authentication, and authorization. Shiro and SpringSecurity each have their advantages, so it’s up to your company to decide which framework to use.