shiro project introduction

shiro project introduction

After the previous shiro integration project, the shiro function will be improved. The previous code will not be shown and introduced. For more information, please refer to the shiro integration project.
Project code acquisition: https://github.com/pysasuke/s...

New function

  • User registration

  • Limit the number of login errors (using redis as a cache)

  • shiro annotation configuration

  • Introduction of DTO

  • Data validation (using hibernate validation)

  • Spring MVC 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 into our custom realm
            request.getSession().setAttribute("user", shiroUser);
            log.info(shiroUser.getUsername() + "Sign in");
            return "user/success";
        } catch (UnknownAccountException e) {
            return "user/login";
        } catch (IncorrectCredentialsException e) {
            request.setAttribute("error", "ERROR Incorrect username or password");
            return "user/login";
        } catch (ExcessiveAttemptsException e) {
            request.setAttribute("error", "Too many password errors,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 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 DTO to Entity and shiro encryption strategy.

 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, the following shows data validation

@Data
public class ShiroUser {
    private Integer id;
    
    @NotNull(message = "User name cannot be empty")
    @Size(min = 3, max = 16, message = "User name length must be between 3-16 Between characters")
    
    private String username;
    
    @NotNull(message = "Password cannot be empty")
    @Size(min = 3, max = 16, message = "{Password length must be between 3-16 Between 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 code is added below.

public class MyRealm extends AuthorizingRealm {

    @Resource
    private ShiroUserService shiroUserService;

    // Granting privileges and roles to current successful users has been successfully logged in
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal(); //Get the username
        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 the username
        ShiroUser shiroUser = shiroUserService.getByUsername(username);
        if (shiroUser != null) {
            SimpleAuthenticationInfo authcInfo = new SimpleAuthenticationInfo(shiroUser.getUsername(), shiroUser.getPassword(), "myRealm");
            //With Simple Authentication Info's credentials Salt, Hashed Credentials Matcher automatically identifies the salt.
            authcInfo.setCredentialsSalt(ByteSource.Util.bytes(shiroUser.getUsername() + shiroUser.getSalt()));
            return authcInfo;
        } else {
            return null;
        }
    }
}
  • constants: Constant class packages

  • dto:DTO Object Package, which shows ShiroUser DTO (contains only fields related to interaction)

@Data
public class ShiroUserDTO {

    @NotNull(message = "User name cannot be empty")
    @Size(min = 3, max = 16, message = "User name length must be between 3-16 Between characters")
    private String username;

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

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

    @NotNull(message = "Please choose gender.")
    private String sex;
}
  • credentials: Handling retry number class packages

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {

    @Autowired
    private RedisCache redisCache;


    //The token credentials matching the user input (not encrypted) and the credentials 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, adding and subtracting operations in a thread-safe manner.
        AtomicInteger retryCount = redisCache.getCache(Constants.USER + username, AtomicInteger.class);
        if (retryCount == null) {
            retryCount = new AtomicInteger(0);
        }
        //increase
        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: spring MVC configuration related files

  • spring-config.xml: Load other integrated configuration files, where spring-mybatis.xml, spring-shiro.xml and db.properties are loaded

  • spring-mybatis.xml: mybatis related configuration file

  • spring-shiro.xml:shiro configuration related files

    <!-- Credential matcher -->
    <bean id="credentialsMatcher" class="com.py.credentials.RetryLimitHashedCredentialsMatcher">
        <!--Specify the hash algorithm as md5,Need the same as when generating passwords-->
        <property name="hashAlgorithmName" value="md5"/>
        <!--The number of hash iterations needed to be the same as when generating passwords-->
        <property name="hashIterations" value="3"/>
        <!--Represents whether the hashed password is stored in hexadecimal system. 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(Main treatment shiro annotation(as@RequiresPermissions)Abnormalities triggered)-->
    <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 mybatis mapping files, for example, UserMapper.xml

  • 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 To configure -->
    <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 To configure, redisTemplate Yes. Jedis Right redis Extension of operation, more operation, encapsulation makes 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 definition - >
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <! -- The defau lt value is false, which means that the life cycle is managed by Spring Application Context, and true means managed by Servlet Container - >.
            <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: deployment 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, '404158848@qq.com', 'male', '26753209835f4c837066d1cc7d9b46aa', '1');
INSERT INTO `t_user` VALUES ('2', 'test', 'a038892c7b638aad0357adb52cabfb29', '2017-08-28', null, '404158848@qq.com', 'male', '6ced07d939407fb0449d92d9f17cfcd1', '2');
INSERT INTO `t_user` VALUES ('3', 'test1', '4be958cccb89213221888f9ffca6969b', '2017-08-28', null, '404158848@qq.com', '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-->

Keywords: Java Redis Shiro Spring xml

Added by RJDavison on Mon, 27 May 2019 23:45:05 +0300