Shiro project introduction

Time:2022-6-7

Shiro project introduction

After the previous Shiro integration project, the Shiro function has been improved. The previous code will not be shown and introduced. Please refer to the Shiro integration project for information
Project code acquisition: https://github.com/pysasuke/s…

New functions

  • User registration

  • Limit of login errors (using redis as cache)

  • Shiro annotation configuration

  • Dto import

  • Data validation (using hibernate validation)

  • Springmvc unified exception handling configuration

Project structure

Java: Code

  • Controller: control layer. The following shows the registration and login functions

@RequestMapping("/login")
    public String login(ShiroUser shiroUser, HttpServletRequest request) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(shiroUser.getUsername(), shiroUser.getPassword());
        try {
            subject. login(token);// Will jump to our customized realm
            request.getSession().setAttribute("user", shiroUser);
            log. Info (shirouser.getusername() + "login");
            return "user/success";
        } catch (UnknownAccountException e) {
            return "user/login";
        } catch (IncorrectCredentialsException e) {
            request. SetAttribute ("error", "wrong user name or password");
            return "user/login";
        } catch (ExcessiveAttemptsException e) {
            request. SetAttribute ("error", "the password is entered incorrectly too many times, please try again later!");
            return "user/login";
        } catch (Exception e) {
            request. SetAttribute ("error", "unknown error");
            return "user/login";
        }
    }
    
    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String register(Model model,
                           @Valid @ModelAttribute ShiroUserDTO shiroUserDTO, BindingResult bindingResult) {
        //Data verification
        if (bindingResult.hasErrors()) {
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            for (ObjectError objectError : allErrors) {
                //Output error message
                System.out.println(objectError.getDefaultMessage());
            }
            model. AddAttribute ("error", "error in filling in information");
            model.addAttribute("user", shiroUserDTO);
            return "/user/register";
        }
        if (shiroUserService.getByUsername(shiroUserDTO.getUsername()) == null) {
            shiroUserService.insertUser(shiroUserDTO);
            return "redirect:/";
        } else {
            model.addAttribute("user", shiroUserDTO);
            model.addAttribute("error", "userName has been registered!");
            return "/user/register";
        }
    }
  • Service: business processing layer. The following shows the new user functions, including data conversion (dto to entity) and password encryption (Shiro encryption policy)

 public void insertUser(ShiroUserDTO shiroUserDTO) {
        ShiroUser shiroUser = converToAddress(shiroUserDTO);
        shiroUserMapper.insert(shiroUser);
    }
  
 private ShiroUser converToAddress(ShiroUserDTO shiroUserDTO) {
        ShiroUser shiroUser = new ShiroUser();
        BeanUtils.copyProperties(shiroUserDTO, shiroUser);
        passwordEncrypt(shiroUser);
        shiroUser.setCreatetime(new Date());
        shiroUser.setRoleId(1);
        return shiroUser;
    }

  private void passwordEncrypt(ShiroUser shiroUser) {
        String username = shiroUser.getUsername();
        String password = shiroUser.getPassword();
        String salt2 = new SecureRandomNumberGenerator().nextBytes().toHex();
        int hashIterations = 3;
        String algorithmName = "md5";
        SimpleHash hash = new SimpleHash(algorithmName, password,
                username + salt2, hashIterations);
        String encodedPassword = hash.toHex();
        shiroUser.setSalt(salt2);
        shiroUser.setPassword(encodedPassword);
    }
  • Dao: database interaction layer

  • Entity: entity object layer, data verification is shown below

@Data
public class ShiroUser {
    private Integer id;
    
    @Notnull (message = "user name cannot be empty")
    @Size (min = 3, max = 16, message = "the user name must be between 3-16 characters long")
    
    private String username;
    
    @Notnull (message = "password cannot be empty")
    @Size (min = 3, max = 16, message = "{password length must be between 3-16 characters")
    private String password;
    
    private Date createtime;
    
    private Date lasttime;
    
    @Email (message = "please enter the correct mailbox")
    private String email;
    
    private String sex;
    
    private String salt;
    
    private Integer roleId;
}
  • Realm: custom realm (Shiro related). Encryption related codes are added below

public class MyRealm extends AuthorizingRealm {

    @Resource
    private ShiroUserService shiroUserService;

    //Grant permissions and roles to users who have successfully logged in. They have successfully logged in
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        String username = (String) principals. getPrimaryPrincipal(); // Get user name
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(shiroUserService.getRoles(username));
        authorizationInfo.setStringPermissions(shiroUserService.getPermissions(username));
        return authorizationInfo;
    }

    //Verify the currently logged in user and obtain authentication information
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        String username = (String) token. getPrincipal(); //  Get user name
        ShiroUser shiroUser = shiroUserService.getByUsername(username);
        if (shiroUser != null) {
            SimpleAuthenticationInfo authcInfo = new SimpleAuthenticationInfo(shiroUser.getUsername(), shiroUser.getPassword(), "myRealm");
            //Set the salt through the credentialssalt of simpleauthenticationinfo, and the hashedcredentialsmatcher will automatically recognize the salt.
            authcInfo.setCredentialsSalt(ByteSource.Util.bytes(shiroUser.getUsername() + shiroUser.getSalt()));
            return authcInfo;
        } else {
            return null;
        }
    }
}
  • Constants: constant class package

  • Dto: dto object package. Shirouserdto is shown below (only interaction related fields are included)

@Data
public class ShiroUserDTO {

    @Notnull (message = "user name cannot be empty")
    @Size (min = 3, max = 16, message = "the user name must be between 3-16 characters long")
    private String username;

    @Notnull (message = "password cannot be empty")
    @Size (min = 3, max = 16, message = "{password length must be between 3-16 characters")
    private String password;

    @Email (message = "please enter the correct mailbox")
    private String email;

    @Notnull (message = "please select gender")
    private String sex;
}
  • Credentials: process retry count class package

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {

    @Autowired
    private RedisCache redisCache;


    //The certificate matching the token entered by the user (unencrypted) and the certificate provided by the system (encrypted)
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        String username = (String) token.getPrincipal();
        //retry count + 1
        //Atomicinteger is an integer class that provides atomic operations. It operates addition and subtraction in a thread safe manner.
        AtomicInteger retryCount = redisCache.getCache(Constants.USER + username, AtomicInteger.class);
        if (retryCount == null) {
            retryCount = new AtomicInteger(0);
        }
        //Growth
        if (retryCount.incrementAndGet() > 5) {
            //if retry count > 5 throw
            throw new ExcessiveAttemptsException();
        }
        redisCache.putCacheWithExpireTime(Constants.USER + username, retryCount, 600);
        boolean matches = super.doCredentialsMatch(token, info);
        if (matches) {
            //clear retry count
            redisCache.deleteCache(Constants.USER + username);
        }
        return matches;
    }
}

Resources: configuration file

  • application. Xml:spring configuration file entry, load spring config xml

  • spring-mvc. Xml:springmvc configuration related files

  • spring-config. Xml: load other integrated configuration files. Here, load spring mybatis xml、spring-shiro. XML and db properties

  • spring-mybatis. Xml:mybatis related configuration files

  • spring-shiro. Xml:shiro configuration related files

<!--  Voucher matcher -- >
    <bean id="credentialsMatcher" class="com.py.credentials.RetryLimitHashedCredentialsMatcher">
        <!-- Specify the hash algorithm as MD5, which should be the same as when generating passwords -- >
        <property name="hashAlgorithmName" value="md5"/>
        <!-- The number of hash iterations should be the same as when generating passwords -- >
        <property name="hashIterations" value="3"/>
        <!-- Indicates whether the hashed password is stored in hexadecimal. It needs to be the same as when generating the password. The default is base64-->
        <property name="storedCredentialsHexEncoded" value="true"/>
    </bean>

    <!--  Custom realm -- >
    <bean id="myRealm" class="com.py.realm.MyRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>
    
     <!-- Spring MVC unified exception handling (mainly dealing with exceptions caused by Shiro annotations (such as @requirespermissions) - >
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <!-- Not logged in -- >
                <prop key="org.apache.shiro.authz.UnauthenticatedException">
                    redirect:/user/login
                </prop>
                <!-- Unauthorized -- >
                <prop key="org.apache.shiro.authz.UnauthorizedException">
                    redirect:/user/login
                </prop>
            </props>
        </property>
        <!-- Default jump page -- >
        <property name="defaultErrorView" value="unauthorized"/>
    </bean>
  • db. Properties: database related parameter configuration

  • log4j. Properties: log related parameter configuration

  • Mapping: store the mybatis mapping file as usermapper XML as an example

  • redis. Properties:redis related parameter configuration

#redis config
redis.pool.maxTotal=100
redis.pool.maxIdle=10
redis.pool.maxWaitMillis=5000
redis.pool.testOnBorrow=true
redis.pool.maxActive= 100
redis.pool.maxWait= 3000


#Redis IP and port number
redis.ip=127.0.0.1
redis.port=6379
redis.pass=
  • spring-redis. Xml:redis related configuration

<!--  Redis configuration -- >
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="${redis.pool.maxTotal}"/>
        <property name="maxIdle" value="${redis.pool.maxIdle}"/>
        <property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}"/>
        <property name="testOnBorrow" value="${redis.pool.testOnBorrow}"/>
    </bean>

    <!--  Redis single node database connection configuration -- >
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.ip}"/>
        <property name="port" value="${redis.port}"/>
        <property name="password" value="${redis.pass}"/>
        <property name="poolConfig" ref="jedisPoolConfig"/>
    </bean>

    <!--  Redistemplate configuration. Redistemplate is an extension of jedis' redis operation. There are more operations. Encapsulation makes the operation more convenient -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
    </bean>
  • spring-mvc-shiro. Xml:shiro annotation related configuration

<!--  Open Shiro annotation -- >
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

Webapp:web related

  • web.xml

<!--  Shiro filter definitions -- >
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <!--  This value defaults to false, which means that the lifecycle is managed by springapplicationcontext. If it is set to true, it means that it is managed by servletcontainer -- >
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

Other documents

Logs: log storage

Deploy: deploy file (SQL)

  • update.sql

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `t_permission`
-- ----------------------------
DROP TABLE IF EXISTS `t_permission`;
CREATE TABLE `t_permission` (
  `id` int(11) NOT NULL,
  `role_id` int(11) NOT NULL,
  `permissionname` varchar(100) COLLATE utf8mb4_bin NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

-- ----------------------------
-- Records of t_permission
-- ----------------------------
INSERT INTO `t_permission` VALUES ('1', '1', 'user:create');
INSERT INTO `t_permission` VALUES ('2', '2', 'user:update');
INSERT INTO `t_permission` VALUES ('3', '1', 'user:update');

-- ----------------------------
-- Table structure for `t_role`
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(20) COLLATE utf8mb4_bin NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

-- ----------------------------
-- Records of t_role
-- ----------------------------
INSERT INTO `t_role` VALUES ('1', 'teacher');
INSERT INTO `t_role` VALUES ('2', 'student');

-- ----------------------------
-- Table structure for `t_user`
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userName` varchar(20) NOT NULL,
  `password` varchar(50) NOT NULL,
  `createTime` date DEFAULT NULL,
  `lastTime` datetime DEFAULT NULL,
  `email` varchar(256) DEFAULT NULL,
  `sex` enum('male','female') DEFAULT 'male',
  `salt` varchar(50) DEFAULT NULL,
  `role_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES ('1', 'admin', '86c4604b628d4e91f5f2a2fed3f88430', '2017-08-28', null, '[email protected]', 'male', '26753209835f4c837066d1cc7d9b46aa', '1');
INSERT INTO `t_user` VALUES ('2', 'test', 'a038892c7b638aad0357adb52cabfb29', '2017-08-28', null, '[email protected]', 'male', '6ced07d939407fb0449d92d9f17cfcd1', '2');
INSERT INTO `t_user` VALUES ('3', 'test1', '4be958cccb89213221888f9ffca6969b', '2017-08-28', null, '[email protected]', 'male', 'c95a278e52daf5166b1ffd6436cde7b7', '1');

pom. Xml:maven correlation

<!-- redis begin-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.6.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-core</artifactId>
            <version>1.0.8</version>
        </dependency>

        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.0.8</version>
        </dependency>
        <!-- redis end-->

        <!--hibernate validation begin-->
        <!--  Spring data verification -- >
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.0.2.Final</version>
        </dependency>
        <!--hibernate validation end-->