In the SpringBoot project, the permission of the current login is controlled by integrating Shiro security framework.
The requirements are as follows:
A page index There are ADD and UPDATE links on HTML. Click the ADD link to jump to ADD HTML page; The same is true for UPDATE links. There are now two users: root and tom. Root user only has ADD access and operation permission, while tom user only has UPDATE access and operation permission.
As shown below:
The content of the page is as follows (without permission):
root login:
Of course, this ADD link can be clicked. After clicking it, ADD will be displayed HTML page content:
tom user login: login with root user. Only the page content is UPDATE
[development environment]:
- IDEA-2020.2
- SpringBoot-2.5.5
- MAVEN-3.5.3
- Shiro
- Thymeleaf
- Mybatis
- Mysql
- Druid
Project structure diagram
backstage:
Reception:
sql file
CREATE TABLE `tab_user` ( `id` varchar(11) NOT NULL, `name` varchar(30) NOT NULL, `pwd` varchar(30) NOT NULL, `perms` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Entity class
public class User { private String id; private String name; private String pwd; private String perms; // getter/setter }
1. Import dependency:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--spring-shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.4.0</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.20</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!--shiro-thymeleaf--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
2. Add application YML configuration:
spring: thymeleaf: cache: false prefix: classpath:/templates/ # Configuration templates (not necessary) are templates by default suffix: .html datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/zzc?useUnicode=true&characterEncoding=utf8 username: root password: root type: com.alibaba.druid.pool.DruidDataSource # SpringBoot does not inject the following attribute values by default initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true # Configure the filters for monitoring statistics interception. After removing the filters, the sql in the monitoring interface cannot be counted, and 'wall' is used for firewall filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 mybatis: type-aliases-package: com.zzc.pojo # Entity class alias mapper-locations: classpath:mapper/*.xml # mapper configuration file (required) configuration: #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # Log output location map-underscore-to-camel-case: true # Hump naming #logging: # level: # com.zzc.mapper: debug # Set log level
3. Background routing controller
@Controller public class RoutingController { @GetMapping({"/", "/index"}) public String index(Model model) { model.addAttribute("msg", "Hello Shior"); return "index"; } @GetMapping("/user/add") public String add() { return "user/add"; } @GetMapping("/user/update") public String update() { return "user/update"; } @GetMapping("/toLogin") public String toLogin() { return "login"; } @PostMapping("/login") public String login(String username, String pwd, Model model) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, pwd); try { subject.login(token); return "index"; } catch (UnknownAccountException e) { model.addAttribute("msg", "user name does not exist"); return "login"; } catch (IncorrectCredentialsException e) { model.addAttribute("msg", "Password error"); return "login"; } } @GetMapping("/noauth") @ResponseBody public String noauth() { return "This page is not authorized"; } }
4. Shiro configuration class ShiroConfig
Like spring security, using Shiro also requires adding a configuration class. Three components need to be configured in this configuration class:
- ShiroFilterFactoryBean
- DefaultWebSecurityManager
- Custom Realm
The code is as follows:
@Configuration public class ShiroConfig { @Bean(name = "shiroFilterFactoryBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean(); // Associate DefaultWebSecurityManager filterFactoryBean.setSecurityManager(defaultWebSecurityManager); // Built in filter /** * anno:Access without authentication * authc:Authentication is required to access * user:I have to remember to visit * perms:You must have permission to access a resource * role:You must have a role permission to access */ Map<String, String> filterMap = new LinkedHashMap<>(); // Configure authorization filterMap.put("/user/add", "perms[user:add]"); filterMap.put("/user/update", "perms[user:update]"); /*filterMap.put("/user/add", "authc"); filterMap.put("/user/update", "authc");*/ filterMap.put("/user/*", "authc"); filterFactoryBean.setFilterChainDefinitionMap(filterMap); // If you do not have permission to access, jump to the login page filterFactoryBean.setLoginUrl("/toLogin"); // If the authorization is not successful, you will jump to the authorization page filterFactoryBean.setUnauthorizedUrl("/noauth"); return filterFactoryBean; } @Bean(name = "defaultWebSecurityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // Associated Realm securityManager.setRealm(userRealm); return securityManager; } // Custom Realm @Bean public UserRealm userRealm() { return new UserRealm(); } }
explain:
- getShiroFilterFactoryBean() method:
filterMap.put("/user/add", "perms[user:add]"); filterMap.put("/user/update", "perms[user:update]");
This code indicates:
- To access the url of / user/add, you need to have the add permission of user
- To access the url of / user/update, you need to have the update permission of user
Format: perms[user:add] is the writing method of permission Operation in Shiro.
filterMap.put("/user/*", "authc");
This code indicates:
- To access the url starting with user, you must authenticate
5. Custom Realm
public class UserRealm extends AuthorizingRealm { @Autowired private UserService userService; // to grant authorization @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Subject subject = SecurityUtils.getSubject(); User currentUser = (User)subject.getPrincipal(); info.addStringPermission(currentUser.getPerms()); return info; } // authentication @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken userToken = (UsernamePasswordToken)authenticationToken; // User name authentication String username = userToken.getUsername(); User user = userService.queryUserByName(username); if (null == user) { // When null is returned, an exception UnknownAccountException will be thrown return null; } // Password authentication: Shiro does it for us. Can encrypt return new SimpleAuthenticationInfo(user, user.getPwd(), ""); } }
explain:
- In the authentication method doGetAuthenticationInfo(), we put the login successful user information into the first parameter in the SimpleAuthenticationInfo construction method, that is: return new SimpleAuthenticationInfo(user, user.getPwd(), ""); User in. Then, when authorizing, we can obtain user information. That is, in the authorization method doGetAuthorizationInfo(), we pass the code: subject subject subject = securityutils getSubject(); User currentUser = (User)subject. getPrincipal(); Get current login user
6. UserMapper interface
Since the code in UserService is similar to that in UserMapper, the code in UserService will be omitted and the UserMapper interface code will be posted directly:
@Repository public interface UserMapper { User queryUserByName(String name); }
The corresponding usermapper XML configuration file, as follows:
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zzc.mapper.UserMapper"> <select id="queryUserByName" parameterType="String" resultType="User"> SELECT id,name,pwd,perms FROM TAB_USER WHERE 1=1 AND name = #{name} </select> </mapper>
Note: also configure Mapper scanning in the main method. As follows:
@MapperScan("com.zzc.mapper") @SpringBootApplication public class ShiroApplication { public static void main( String[] args ) { SpringApplication.run(ShiroApplication.class, args); } }
7. Home page index html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"> <title>home page</title> </head> <body> <h1 >Hello Shiro</h1> <p th:text="${msg}">12</p> <p> <a th:href="@{/toLogin}">Sign in</a> </p> <hr> <a th:href="@{/user/add}">ADD</a> <a th:href="@{/user/update}">UPDATE</a> </body> </html>
8. Login page login html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Sign in</title> </head> <body> <h1>Sign in</h1><hr> <p th:text="${msg}" style="color: red"></p> <form th:action="@{/login}" method="post"> <table> <tr> <td>user name</td> <td> <input type="text" name="username" /> </td> </tr> <tr> <td>password</td> <td> <input type="password" name="pwd" /> </td> </tr> <tr> <td colspan="2"> <input type="submit" value="Sign in" /> </td> </tr> </table> </form> </body> </html>
In fact, most of the requirements have been completed here. There is one problem left: the root user only has user:add permission, so it should not display user:update. Namely:
Moreover, when we click the UPDATE link, we will find that we do not have permission (this is normal):
Therefore, the front desk needs to be processed here! Only show the content that the current user has permission!!
Shiro needs to be integrated through Thymeleaf
9-1. Import dependency (imported above)
<dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
9-2. Modify the configuration class ShiroConfig
Need to register a bean --- shirodialect
Add a Bean to the original configuration class:
// Shiro-Thymeleaf @Bean public ShiroDialect getShiroDialect() { return new ShiroDialect(); }
9-3. Modify user realm
Only modify the method doGetAuthenticationInfo().
After the user logs in successfully, the user information is stored in the Session
// authentication @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken userToken = (UsernamePasswordToken)authenticationToken; // User name authentication String username = userToken.getUsername(); User user = userService.queryUserByName(username); if (null == user) { // When null is returned, an exception UnknownAccountException will be thrown return null; } // After successful login, put the current login user into the Session Subject currentSubject = SecurityUtils.getSubject(); Session session = currentSubject.getSession(); session.setAttribute("user", user); // Password authentication: Shiro does it for us. Can encrypt return new SimpleAuthenticationInfo(user, user.getPwd(), ""); }
9-4. Modify the homepage index html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"> <title>home page</title> </head> <body> <h1 >Hello Shiro</h1> <p th:text="${msg}">12</p> <div th:if="${session.user == null}"> <p> <a th:href="@{/toLogin}">Sign in</a> </p> </div> <hr> <div shiro:hasPermission="user:add"> <a th:href="@{/user/add}">ADD</a> </div> <div shiro:hasPermission="user:update"> <a th:href="@{/user/update}">UPDATE</a> </div> </body> </html>
Well, SpringBoot integration Shiro is here~~