catalogue
1. Create a SpringBoot project
4. controller layer programming
5. Implement authentication in UserRealm
Write resource access restrictions in ShiroConfig
6. New service layer and SaltUtil
3. Write ApplicationContextUtil
4, Shiro role based authorization
2. Write entity classes for User and Role
5. Implement authorization in Realm
5, Shiro permission based authorization
1. New table new t_perm and t_role_permn table
2. Write Role and Perms entity classes
6, EhCache implementation cache
Shiro's introduction and basic use will not be introduced here
To get started, go to: Shiro quick start
1, Construction project
1. Create a SpringBoot project
- When creating a new SpringBoot project, check web, thymeleaf and lombok
Import dependent
<!--shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency>
2. Create configuration class
Custom UserRealm
public class UerRealm extends AuthorizingRealm { //to grant authorization @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("Authorization executed"); return null; } //authentication @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("Certification performed"); return null; } }
ShiroConfig
This class is the core configuration class of Shiro, which inherits ShiroFilter, SecurityManager and the above customized Realm
@Configuration public class ShiroConfig { //1. Create shiroFilter. / / it is responsible for intercepting all requests @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean(); //Set up security manager bean.setSecurityManager(defaultWebSecurityManager); return bean; } //DefaultWebSecurityManager @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm uerRealm){ DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager(); //Associate CustomerRealm securityManager.setRealm(uerRealm); return securityManager; } //Create a realm object @Bean public UserRealm getRealm(){ return new UserRealm(); } }
Common filters in Shiro
Configuration abbreviation | Corresponding filter | function |
---|---|---|
anon | AnonymousFilter | Specifies that the url can be accessed anonymously |
authc | FormAuthenticationFilter | The specified url requires form login. By default, the user name, password,rememberMe and other parameters will be obtained from the request and try to log in. If the login fails, it will jump to the path configured by loginUrl. We can also use this filter as the default login logic, but we usually write the login logic in the controller ourselves. If we write it ourselves, the error returned information can be customized. |
authcBasic | BasicHttpAuthenticationFilter | The specified url requires basic login |
logout | LogoutFilter | Log out of the filter and configure the specified url to realize the exit function, which is very convenient |
noSessionCreation | NoSessionCreationFilter | Disable session creation |
perms | PermissionsAuthorizationFilter | You need to specify permissions to access |
port | PortFilter | You need to specify a port to access |
rest | HttpMethodPermissionFilter | Convert the http request method into the corresponding verb to construct a permission string. This doesn't make much sense. I'm interested in reading the comments of the source code |
roles | RolesAuthorizationFilter | You need to specify a role to access |
ssl | SslFilter | An https request is required to access |
user | UserFilter | Users who are logged in or remember me are required to access |
3. index and login pages
<!DOCTYPE html> <html lang="en" xmlns:th="https://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>System home page</h1> <ul> <li><a href="">user management </a></li> <li><a href="">Order management</a></li> </ul> </body> </html>
<!DOCTYPE html> <html lang="en" xmlns:th="https://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>User login</h1> <form th:action="${/login}" method="post"> user name:<input type="text" name="username"> <br/> password: <input type="text" name="password"> <br> <input type="submit" value="Sign in"> </form> </body> </html>
4. controller layer programming
@Controller public class MyController { @RequestMapping("/toLogin") public String toLogin(){ return "login"; } @RequestMapping("/toIndex") public String toLogin(){ return "index"; } @RequestMapping("/login") public String login(String username,String password){ // Get current login user Subject subject = SecurityUtils.getSubject(); try { // Perform login operation subject.login(new UsernamePasswordToken(username,password)); // After passing the authentication, directly jump to index.html return "redirect:/toIndex"; } catch (UnknownAccountException e) { e.printStackTrace(); System.out.println("User name error~"); } catch (IncorrectCredentialsException e) { e.printStackTrace(); System.out.println("Password error~"); } catch (Exception e) { e.printStackTrace(); } // If the authentication fails, you still return to the login page return "redirect:/toLogin"; } }
5. Implement authentication in UserRealm
public class UserRealm extends AuthorizingRealm { // to grant authorization @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } // authentication @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // Get the currently logged in topic String principal = (String) token.getPrincipal(); // Simulate the data returned by the database if("admin".equals(principal)){ return new SimpleAuthenticationInfo(principal,"123456",this.getName()); } return null; } }
In the above authentication, as long as the user name we enter is admin and the password is 123456, we can enter the home page through authentication
Write resource access restrictions in ShiroConfig
@Configuration public class ShiroConfig { //1. Create shiroFilter. / / it is responsible for intercepting all requests @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean(); //Set up security manager bean.setSecurityManager(defaultWebSecurityManager); //Configure system restricted resources //Configure system public resources Map<String,String> map = new HashMap<String,String>(); map.put("/toIndex","anon"); map.put("/toLogin","anon"); // When anon is set as a public resource, pay attention to the order of anon and authc map.put("/","authc"); //authc requires authentication and authorization to request this resource map.put("/index","authc"); //authc requires authentication and authorization to request this resource //Default authentication interface path shiroFilterFactoryBean.setLoginUrl("/toLogin"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return bean; } //DefaultWebSecurityManager @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm uerRealm){...} //Create a realm object @Bean public UserRealm userRealm(){...} }
Test: it can be found that you cannot access / and / index directly without logging in, and you will jump to the login interface
2, MD5, Salt registration
1. New register.html
<!DOCTYPE html> <html lang="en" xmlns:th="https://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>User registration</h1> <form th:action="${/register}" method="post"> user name:<input type="text" name="username"> <br/> password: <input type="text" name="password"> <br> <input type="submit" value="Register now"> </form> </body> </html>
2. New table t_user
DROP TABLE IF EXISTS `t_user`; create table `t_user` ( `id` int (11), `username` varchar (32), `password` varchar (32), `salt` varchar (32), );
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <!-- mybatis plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency>
3,application.yml
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC username: root password: 123456 # filters for monitoring statistical interception filters: stat,wall,log4j,config # Configure initialization size / min / Max initial-size: 5 min-idle: 5 max-active: 20 # Get connection wait timeout max-wait: 60000 # How often is the test performed to detect idle connections that need to be closed time-between-eviction-runs-millis: 60000 # Minimum lifetime of a connection in the pool min-evictable-idle-time-millis: 300000 validation-query: SELECT 'x' test-while-idle: true test-on-borrow: false test-on-return: false # Open PSCache and specify the size of PSCache on each connection. Set oracle to true and mysql to false. There are many sub databases and sub tables. It is recommended to set it to false pool-prepared-statements: false max-pool-prepared-statement-per-connection-size: 20 mybatis-plus: type-aliases-package: com.christy.shiro.entity configuration: map-underscore-to-camel-case: true
4. New entity class
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { /** The annotation cannot be less when the field self increment is set in the database**/ @TableId(type = IdType.AUTO) private Integer id; private String username; private String password; private String salt; }
5. New UserMapper
@Mapper public interface UserMapper extends BaseMapper<User> { }
6. New service layer and SaltUtil
(1)UserService
public interface UserService { void register(User user); }
(2)UserServiceImpl
@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public void register(User user) { // Generate random salt String salt = SaltUtil.getSalt(ShiroConstant.SALT_LENGTH); // Save random salt user.setSalt(salt); // Generate password Md5Hash password = new Md5Hash(user.getPassword(), salt, ShiroConstant.HASH_ITERATORS); // Save password user.setPassword(password.toHex()); userMapper.insert(user); } }
(3)SaltUtil
/** * User random salt generation tool class */ public class SaltUtil { public static String getSalt(int n){ char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".toCharArray(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < n; i++) { char aChar = chars[new Random().nextInt(chars.length)]; sb.append(aChar); } return sb.toString(); } }
(4)ShiroConstant
public class ShiroConstant { /** Number of random salt**/ public static final int SALT_LENGTH = 8; /** hash Number of hashes**/ public static final int HASH_ITERATORS = 1024; /** Encryption mode**/ public interface HASH_ALGORITHM_NAME { String MD5 = "MD5"; } }
7. Write Controller
@Controller public class MyController { @Autowired private UserService userService; @RequestMapping("/toLogin") public String toLogin(){...} @RequestMapping("/toRegister") public String toRegister(){...} @RequestMapping("/toIndex") public String toLogin(){...} @RequestMapping("/login") public String login(String username,String password){...} @RequestMapping("/register") public String register(User user){ try { userService.register(user); return "redirect:/login.jsp"; } catch (Exception e) { e.printStackTrace(); } return "redirect:/register.jsp"; } }
8. Write ShiroConfig
@Configuration public class ShiroConfig { //1. Create shiroFilter. / / it is responsible for intercepting all requests @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean(); //Set up security manager bean.setSecurityManager(defaultWebSecurityManager); //Configure system restricted resources //Configure system public resources Map<String,String> map = new HashMap<String,String>(); // When anon is set as a public resource, pay attention to the order of anon and authc map.put("/toIndex","anon"); map.put("/toLogin","anon"); map.put("/register","anon"); map.put("/toRegister","anon"); map.put("/","authc"); map.put("/index","authc"); //authc requires authentication and authorization to request this resource //Default authentication interface path shiroFilterFactoryBean.setLoginUrl("/toLogin"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return bean; } //DefaultWebSecurityManager @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm uerRealm){...} //Create a realm object @Bean public UserRealm userRealm(){...} }
Restart the project test: you can see that the password saved to the database by the registered department user is encrypted
3, MD5, Salt certification
1. Write Service layer
public interface UserService { ......Omit other methods User findUserByUserName(String userName); }
@Service("userService") public class UserServiceImpl implements UserService { ......Omit other methods @Override public User findUserByUserName(String userName) { return userMapper.findUserByUsername(userName); } }
2. Write UserRealm
public class UserRealm extends AuthorizingRealm { // to grant authorization @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } // authentication @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // Get the currently logged in user name String principal = (String) token.getPrincipal(); // Since CustomerRealm is not managed by the factory, it cannot be managed by UserService UserService userService = (UserService) ApplicationContextUtil.getBean("userService"); User user = userService.findUserByUserName(principal); if(!ObjectUtils.isEmpty(user)){ return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), new CustomerByteSource(user.getSalt()),this.getName()); } return null; } }
3. Write ApplicationContextUtil
@Component public class ApplicationContextUtil implements ApplicationContextAware { public static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } /** * Get the class instance according to the class name in the factory */ public static Object getBean(String beanName){ return context.getBean(beanName); } }
4. Write ShiroConfig
@Configuration public class ShiroConfiguration { ......Omit other methods @Bean public UserRealm getRealm(){ UserRealm userRealm = new UserRealm(); // Set password matcher HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); // Set encryption method credentialsMatcher.setHashAlgorithmName(ShiroConstant.HASH_ALGORITHM_NAME.MD5); // Sets the number of hashes credentialsMatcher.setHashIterations(ShiroConstant.HASH_ITERATORS); customerRealm.setCredentialsMatcher(credentialsMatcher); return uerRealm; } }
Restart the project test: you can see that all accounts can log in
4, Shiro role based authorization
1. Create table
# The user table has been created before, so it will not be created here: t_user DROP TABLE IF EXISTS `t_role`; CREATE TABLE `t_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; /*Table structure for table `t_user_role` */ DROP TABLE IF EXISTS `t_user_role`; CREATE TABLE `t_user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(8) DEFAULT NULL, `role_id` int(8) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
t_user
t_role
t_user_role
2. Write entity classes for User and Role
@Data @NoArgsConstructor @AllArgsConstructor public class User{ /** Other attributes are omitted**/ private List<Role> roles = new ArrayList<>(); }
@Data @NoArgsConstructor @AllArgsConstructor public class Role{ /** The annotation cannot be less when the field self increment is set in the database**/ @TableId(type = IdType.AUTO) private Integer id; private String name; }
3. Authoring Mapper layers
@Mapper public interface UserMapper extends BaseMapper<User> { @Select("SELECT u.id,u.username,u.password,u.salt FROM t_user u WHERE u.username = #{username}") User findUserByUsername(String username); }
@Mapper public interface RoleMapper extends BaseMapper<Role> { @Select("select r.id,r.name from t_role r left join t_user_role ur on ur.role_id = r.id where ur.user_id = #{userId}") List<Role> getRolesByUserId(Integer userId); }
4. Write Service layer
New RoleService and RoleServiceImpl
public interface RoleService { List<Role> getRolesByUserId(Integer userId); }
@Service("roleService") public class RoleServiceImpl implements RoleService { @Autowired private RoleMapper roleMapper; @Override public List<Role> getRolesByUserId(Integer userId) { return roleMapper.getRolesByUserId(userId); } }
5. Implement authorization in Realm
public class UserRealm extends AuthorizingRealm { // to grant authorization @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // Get primary identity information String principal = (String) principals.getPrimaryPrincipal(); // According to user information UserService userService = (UserService) ApplicationContextUtil.getBean("userService"); User user = userService.findUserByUserName(principal); //Obtain role information according to user id RoleService roleService = (RoleService) ApplicationContextUtil.getBean("roleService"); List<Role> roles = roleService.getRolesByUserId(user.getId()); //If the role information is not empty, add the role information if(!CollectionUtils.isEmpty(roles)){ SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); roles.forEach(role -> { simpleAuthorizationInfo.addRole(role.getName()); }); return simpleAuthorizationInfo; } return null; } /** Authentication code omitted**/ }
6. Write Index page
Please import the following dependencies before writing the page
<!--thymeleaf Template--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
<!DOCTYPE html> <html lang="en" xmlns:th="https://www.thymeleaf.org" xmlns:shiro="https://www.thymeleaf.org/thymeleaf-extras-shiro"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>System home page</h1> <%--Need to import: xmlns:shiro="https://www.thymeleaf.org/thymeleaf-extras-shiro"--%> <%-- admin Users in the role can have the permissions of user management and order management at the same time, user Users in this role only have permission to manage orders --%> <ul> <li shiro:hasAnyRole="admin,user"><a href="">Order management</a></li> <li shiro:hasRole="admin"><a href="">user management </a></li> </ul> </body> </html>
Restart project test
5, Shiro permission based authorization
1. New table new t_perm and t_role_permn table
DROP TABLE IF EXISTS `t_perms`; CREATE TABLE `t_perms` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(128) DEFAULT NULL, `url` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `t_role_perms`; CREATE TABLE `t_role_perms` ( `id` int(11) NOT NULL AUTO_INCREMENT, `role_id` int(11) DEFAULT NULL, `perms_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
t_perms
t_role_perms
2. Write Role and Perms entity classes
@Data @NoArgsConstructor @AllArgsConstructor public class Role { /** Other attribute fields are omitted**/ private List<Permission> permissions = new ArrayList<>(); }
@Data @NoArgsConstructor @AllArgsConstructor public class Perms{ /** The annotation cannot be less when the field self increment is set in the database**/ @TableId(type = IdType.AUTO) private Integer id; private String name; private String url; }
3. Authoring Mapper layers
@Mapper public interface PermsMapper extends BaseMapper<Perms> { @Select("select p.id,p.name,p.url from t_perms p left join t_role_perms rp on rp.perms_id = p.id where rp.role_id = #{roleId}") List<Perms> getPermssByRoleId(Integer roleId); }
4. Write Service layer
public interface PermsService { List<Perms> getPermsByRoleId(Integer roleId); }
@Service("permissionService") public class PermsServiceImpl implements PermsService { @Autowired private PermsMapper permsMapper; @Override public List<Perms> getPermssByRoleId(Integer roleId) { return permsMapper.getPermssByRoleId(roleId); } }
5. Write userRealm
public class UserRealm extends AuthorizingRealm { // to grant authorization @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // Get primary identity information String principal = (String) principals.getPrimaryPrincipal(); // Obtain role information according to master identity information UserService userService = (UserService) ApplicationContextUtil.getBean("userService"); User user = userService.findUserByUserName(principal); RoleService roleService = (RoleService) ApplicationContextUtil.getBean("roleService"); List<Role> roles = roleService.getRolesByUserId(user.getId()); if(!CollectionUtils.isEmpty(roles)){ SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); roles.forEach(role -> { simpleAuthorizationInfo.addRole(role.getName()); PermissionService permissionService = (PermissionService) ApplicationContextUtil.getBean("permissionService"); List<Permission> permissions = permissionService.getPermissionsByRoleId(role.getId()); if(!CollectionUtils.isEmpty(permissions)){ permissions.forEach(permission -> { simpleAuthorizationInfo.addStringPermission(permission.getName()); }); } }); return simpleAuthorizationInfo; } return null; } /** Authentication code omitted**/ }
6. Write Index page
<!DOCTYPE html> <html lang="en" xmlns:th="https://www.thymeleaf.org" xmlns:shiro="https://www.thymeleaf.org/thymeleaf-extras-shiro"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>System home page</h1> <%--Need to import: xmlns:shiro="https://www.thymeleaf.org/thymeleaf-extras-shiro"--%> <%-- admin Users in the role can have the permissions of user management and order management at the same time, user Users in this role only have permission to manage orders --%> <ul> <li shiro:hasAnyRole="admin,user"><a href="">Order management</a></li> <li shiro:hasRole="admin"><a href="">user management </a></li> </ul> <div shiro:hasPermission="user:add:*"> <a th:href="@{/user/add}">increase</a> </div> <div shiro:hasPermission="admin:update:*"> <a th:href="@{/user/update}">modify</a> </div> <div shiro:hasPermission="admin:delete:*"> <a th:href="@{/user/update}">delete</a> </div> <div shiro:hasPermission="user:select:*"> <a th:href="@{/user/update}">query</a> </div> </body> </html>
Restart project test
6, EhCache implementation cache
Still learning
If there is any mistake, I hope someone can point it out and spray it gently