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-->