在以前的shiro整合項目以後,更加完善shiro功能,以前的代碼不予展現與介紹,想了解的請參考shiro整合項目
項目代碼獲取:https://github.com/pysasuke/s...java
用戶註冊git
登陸錯誤次數限制(使用redis做緩存)github
shiro註解配置web
DTO引入redis
數據校驗(使用hibernate validation)算法
SpringMVC統一異常處理配置spring
controller:控制層,如下展現註冊和登陸功能sql
@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);//會跳到咱們自定義的realm中 request.getSession().setAttribute("user", shiroUser); log.info(shiroUser.getUsername() + "登陸"); return "user/success"; } catch (UnknownAccountException e) { return "user/login"; } catch (IncorrectCredentialsException e) { request.setAttribute("error", "用戶名或密碼錯誤"); return "user/login"; } catch (ExcessiveAttemptsException e) { request.setAttribute("error", "輸入密碼錯誤太屢次,請稍後再試!"); return "user/login"; } catch (Exception e) { request.setAttribute("error", "未知錯誤"); return "user/login"; } } @RequestMapping(value = "/register", method = RequestMethod.POST) public String register(Model model, @Valid @ModelAttribute ShiroUserDTO shiroUserDTO, BindingResult bindingResult) { //數據校驗 if (bindingResult.hasErrors()) { List<ObjectError> allErrors = bindingResult.getAllErrors(); for (ObjectError objectError : allErrors) { //輸出錯誤信息 System.out.println(objectError.getDefaultMessage()); } model.addAttribute("error", "填入信息有誤"); 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:業務處理層,如下展現新增用戶功能,包含數據轉換(DTO到Entity)和密碼加密(shiro加密策略)數據庫
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:數據庫交互層apache
entity:實體對象層,如下展現數據校驗
@Data public class ShiroUser { private Integer id; @NotNull(message = "用戶名不能爲空") @Size(min = 3, max = 16, message = "用戶名長度必須介於3-16個字符之間") private String username; @NotNull(message = "密碼不能爲空") @Size(min = 3, max = 16, message = "{密碼長度必須介於3-16個字符之間") private String password; private Date createtime; private Date lasttime; @Email(message = "請輸入正確的郵箱") private String email; private String sex; private String salt; private Integer roleId; }
realm:自定義Realm(shiro相關),如下加入了加密相關代碼
public class MyRealm extends AuthorizingRealm { @Resource private ShiroUserService shiroUserService; // 爲當前登錄成功的用戶授予權限和角色,已經登錄成功了 @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); //獲取用戶名 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.setRoles(shiroUserService.getRoles(username)); authorizationInfo.setStringPermissions(shiroUserService.getPermissions(username)); return authorizationInfo; } // 驗證當前登陸的用戶,獲取認證信息 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); // 獲取用戶名 ShiroUser shiroUser = shiroUserService.getByUsername(username); if (shiroUser != null) { SimpleAuthenticationInfo authcInfo = new SimpleAuthenticationInfo(shiroUser.getUsername(), shiroUser.getPassword(), "myRealm"); //經過SimpleAuthenticationInfo的credentialsSalt設置鹽,HashedCredentialsMatcher會自動識別這個鹽。 authcInfo.setCredentialsSalt(ByteSource.Util.bytes(shiroUser.getUsername() + shiroUser.getSalt())); return authcInfo; } else { return null; } } }
constants:常量類包
dto:DTO對象包,如下展現ShiroUserDTO(只包含交互相關的字段)
@Data public class ShiroUserDTO { @NotNull(message = "用戶名不能爲空") @Size(min = 3, max = 16, message = "用戶名長度必須介於3-16個字符之間") private String username; @NotNull(message = "密碼不能爲空") @Size(min = 3, max = 16, message = "{密碼長度必須介於3-16個字符之間") private String password; @Email(message = "請輸入正確的郵箱") private String email; @NotNull(message = "請選擇性別") private String sex; }
credentials:處理重試次數類包
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher { @Autowired private RedisCache redisCache; //匹配用戶輸入的token的憑證(未加密)與系統提供的憑證(已加密) @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { String username = (String) token.getPrincipal(); //retry count + 1 //AtomicInteger是一個提供原子操做的Integer類,經過線程安全的方式操做加減。 AtomicInteger retryCount = redisCache.getCache(Constants.USER + username, AtomicInteger.class); if (retryCount == null) { retryCount = new AtomicInteger(0); } //增加 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; } }
application.xml:spring配置文件入口,加載spring-config.xml
spring-mvc.xml:springmvc配置相關文件
spring-config.xml:加載其餘集成的配置文件,這裏加載spring-mybatis.xml、spring-shiro.xml和db.properties
spring-mybatis.xml:mybatis相關配置文件
spring-shiro.xml:shiro配置相關文件
<!-- 憑證匹配器 --> <bean id="credentialsMatcher" class="com.py.credentials.RetryLimitHashedCredentialsMatcher"> <!--指定散列算法爲md5,須要和生成密碼時的同樣--> <property name="hashAlgorithmName" value="md5"/> <!--散列迭代次數,須要和生成密碼時的同樣--> <property name="hashIterations" value="3"/> <!--表示是否存儲散列後的密碼爲16進制,須要和生成密碼時的同樣,默認是base64--> <property name="storedCredentialsHexEncoded" value="true"/> </bean> <!-- 自定義Realm --> <bean id="myRealm" class="com.py.realm.MyRealm"> <property name="credentialsMatcher" ref="credentialsMatcher"/> </bean> <!--Spring MVC統一異常處理(主要處理shiro註解(如@RequiresPermissions)引起的異常)--> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <!--未登陸--> <prop key="org.apache.shiro.authz.UnauthenticatedException"> redirect:/user/login </prop> <!--未受權--> <prop key="org.apache.shiro.authz.UnauthorizedException"> redirect:/user/login </prop> </props> </property> <!--默認跳轉頁面--> <property name="defaultErrorView" value="unauthorized"/> </bean>
db.properties:數據庫相關參數配置
log4j.properties:日誌相關參數配置
mapping:存放mybatis映射文件,以UserMapper.xml爲例
redis.properties:redis相關參數配置
#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和端口號 redis.ip=127.0.0.1 redis.port=6379 redis.pass=
spring-redis.xml:redis相關配置
<!-- Redis 配置 --> <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單節點數據庫鏈接配置 --> <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配置,redisTemplate是對Jedis的對redis操做的擴展,有更多的操做,封裝使操做更便捷 --> <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory"/> </bean>
spring-mvc-shiro.xml:shiro註解相關配置
<!-- 開啓Shiro註解 --> <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>
web.xml
<!-- shiro過濾器定義 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 該值缺省爲false,表示生命週期由SpringApplicationContext管理,設置爲true則表示由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>
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');
<!-- 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 數據校驗--> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.0.2.Final</version> </dependency> <!--hibernate validation end-->