- db
採用RBAC模式,其核心爲用戶-角色-權限三表。
css
- pom.xmlhtml
首先核心dependency以下(須要AOP依賴):java
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <!-- aop依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
可選依賴:web
<!-- shiro-redis --> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>3.2.2</version> </dependency>
- ShiroConfig.javaredis
package com.demo.shiro; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import org.apache.shiro.codec.Base64; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.SessionListener; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.crazycake.shiro.RedisCacheManager; import org.crazycake.shiro.RedisManager; import org.crazycake.shiro.RedisSessionDAO; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; //import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; @Configuration public class ShiroConfig { /** * Shiro的Web過濾器Factory 命名:shiroFilter */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 設置 securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 登陸的 url shiroFilterFactoryBean.setLoginUrl("/login"); // 登陸成功後跳轉的 url shiroFilterFactoryBean.setSuccessUrl("/index"); // 未受權 url shiroFilterFactoryBean.setUnauthorizedUrl("/403"); LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 設置免認證 url filterChainDefinitionMap.put("/jsonTest", "anon"); filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/photos/**", "anon"); filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/guest/**", "anon"); // } // 配置退出過濾器,其中具體的退出代碼 Shiro已經替咱們實現了 //filterChainDefinitionMap.put("/logout", "logout"); // 除上之外全部 url都必須認證經過才能夠訪問,未經過認證自動訪問 LoginUrl filterChainDefinitionMap.put("/**", "user"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } // 注入自定義的realm,告訴shiro如何獲取用戶信息來作登陸或權限控制 @Bean public CustomRealm realm() { return new CustomRealm(); } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 配置 SecurityManager,並注入 shiroRealm securityManager.setRealm(realm()); // 配置 rememberMeCookie securityManager.setRememberMeManager(rememberMeManager()); // 配置 緩存管理類 cacheManager securityManager.setCacheManager(cacheManager()); securityManager.setSessionManager(sessionManager()); return securityManager; } @Bean(name = "lifecycleBeanPostProcessor") public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { // shiro 生命週期處理器 return new LifecycleBeanPostProcessor(); } /** * DefaultAdvisorAutoProxyCreator 和 AuthorizationAttributeSourceAdvisor 用於開啓 * shiro 註解的使用 如 @RequiresAuthentication, @RequiresUser, @RequiresPermissions 等 * * @return DefaultAdvisorAutoProxyCreator */ @Bean @DependsOn({ "lifecycleBeanPostProcessor" }) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 用於開啓 Thymeleaf 中的 shiro 標籤的使用 * * @return ShiroDialect shiro 方言對象 */ // @Bean // public ShiroDialect shiroDialect() { // return new ShiroDialect(); // } /** * shiro 中配置 redis 緩存 * * @return RedisManager */ private RedisManager redisManager() { RedisManager redisManager = new RedisManager(); return redisManager; } /** * shiro 中配置 redis cache緩存 * * @return RedisCacheManager */ private RedisCacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); return redisCacheManager; } /** * rememberMe cookie 效果是重開瀏覽器後無需從新登陸 * * @return SimpleCookie */ private SimpleCookie rememberMeCookie() { // 設置 cookie 名稱,對應 login.html 頁面的 <input type="checkbox" name="rememberMe"/> SimpleCookie cookie = new SimpleCookie("rememberMe"); // cookie.setSecure(true); // 只在 https中有效 註釋掉 正常 // 設置 cookie 的過時時間,單位爲秒,這裏爲一天 cookie.setMaxAge(2000); return cookie; } /** * cookie管理對象 * * @return CookieRememberMeManager */ private CookieRememberMeManager rememberMeManager() { CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); // rememberMe cookie 加密的密鑰 cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag==")); return cookieRememberMeManager; } @Bean public RedisSessionDAO redisSessionDAO() { RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); return redisSessionDAO; } /** * session 管理對象 * * @return DefaultWebSessionManager */ @Bean public DefaultWebSessionManager sessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); Collection<SessionListener> listeners = new ArrayList<>(); listeners.add(new ShiroSessionListener()); // 設置session超時時間,單位爲毫秒 sessionManager.setGlobalSessionTimeout(2000000); sessionManager.setSessionListeners(listeners); sessionManager.setSessionDAO(redisSessionDAO()); return sessionManager; } }
package com.demo.shiro; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.shiro.authc.AccountException; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.dubbo.config.annotation.Reference; import com.demo.entity.Permission; import com.demo.entity.Role; import com.demo.entity.User; import com.demo.service.IPermissionService; import com.demo.service.IRoleService; import com.demo.service.IUserService; public class CustomRealm extends AuthorizingRealm { private static final Logger log = LoggerFactory.getLogger(CustomRealm.class); @Reference(version = "1.0.0") private IUserService iUserService; @Reference(version = "1.0.0") private IRoleService iRoleService; @Reference(version = "1.0.0") private IPermissionService iPermissionService; //定義如何獲取用戶的角色和權限的邏輯,給shiro作權限判斷 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { log.info("------------權限認證---------"); User user = (User) getAvailablePrincipal(principals); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); List<Role> list = this.iRoleService.findUserRole(user.getNickname()); Set<String> set = new HashSet<String>(); for (Role r : list) { set.add(r.getName()); } Set<String> perms = new HashSet<String>(); List<Permission> list1 = this.iPermissionService.findRolePerm(user.getNickname()); for(Permission p : list1) { perms.add(p.getUrl()); } info.setRoles(set);//添加角色集合 @RequireRoles("admin")會到info中尋找 字符串 "admin" info.setStringPermissions(perms);// 添加權限集合 @RequiresPermissions("test") 會到info中尋找字符串"test" return info; } // 定義如何獲取用戶信息的業務邏輯,給shiro作登陸 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { log.info("------------身份認證方法---------"); UsernamePasswordToken token = (UsernamePasswordToken) authcToken; String nickname = token.getUsername(); if (nickname == null) { throw new AccountException("Null usernames are not allowed by this realm."); } User user = this.iUserService.findByNickName(nickname); // 從數據庫獲取對應用戶名密碼的用戶 if (user == null) { throw new UnknownAccountException("No account found for admin [" + nickname + "]"); } if(user.getStatus() == 0) { throw new LockedAccountException("您的帳號被禁止登陸,請聯繫管理員"); } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPswd(), getName()); return info; } }
- ShiroSessionListener.javaspring
package com.demo.shiro; import java.util.concurrent.atomic.AtomicInteger; import org.apache.shiro.session.Session; import org.apache.shiro.session.SessionListener; public class ShiroSessionListener implements SessionListener{ private final AtomicInteger sessionCount = new AtomicInteger(0); @Override public void onStart(Session session) { sessionCount.incrementAndGet(); } @Override public void onStop(Session session) { sessionCount.decrementAndGet(); } @Override public void onExpiration(Session session) { sessionCount.decrementAndGet(); } }
package com.demo.handler; import org.apache.shiro.ShiroException; import org.apache.shiro.authz.UnauthenticatedException; import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import com.demo.domain.Codes; import com.demo.domain.Json; /** * 統一捕捉shiro的異常,返回給前臺一個json信息,前臺根據這個信息顯示對應的提示,或者作頁面的跳轉。 */ @ControllerAdvice public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); //不知足@RequiresGuest註解時拋出的異常信息 private static final String GUEST_ONLY = "Attempting to perform a guest-only operation"; @ExceptionHandler(ShiroException.class) @ResponseBody public Json handleShiroException(ShiroException e) { String eName = e.getClass().getSimpleName(); log.error("shiro執行出錯:{}",eName); return new Json(eName, false, Codes.SHIRO_ERR, "鑑權或受權過程出錯", null); } @ExceptionHandler(UnauthenticatedException.class) @ResponseBody public Json page401(UnauthenticatedException e) { String eMsg = e.getMessage(); if (StringUtils.startsWithIgnoreCase(eMsg,GUEST_ONLY)){ return new Json("401", false, Codes.UNAUTHEN, "只容許遊客訪問,若您已登陸,請先退出登陸", null) .data("detail",e.getMessage()); }else{ return new Json("401", false, Codes.UNAUTHEN, "用戶未登陸", null) .data("detail",e.getMessage()); } } @ExceptionHandler(UnauthorizedException.class) @ResponseBody public Json page403() { return new Json("403", false, Codes.UNAUTHZ, "用戶沒有訪問權限", null); } }
- 測試
RequiresPermissions("/share")//表明該類下全部方法的訪問須要 登陸用戶擁有 權限 「/share」,可類比至方法註解等。
public class ShareController {
…//
}數據庫
@RequestMapping(value = { "/login" }, method = { RequestMethod.POST }, consumes = { "application/json" }, produces = "application/json;charset=UTF-8") public @ResponseBody Json login(@RequestBody User user) {//Json 爲封裝好的返回對象,能夠本身設置 log.info("==========================/login=================================="); String oper = "user login : "; JSONObject responseObj = (JSONObject) JSONObject.toJSON(user); log.info(oper + responseObj); String nickname = responseObj.getString("nickname"); String pswd = responseObj.getString("pswd"); if (StringUtils.isEmpty(nickname)) { return Json.fail(oper, "用戶名不能爲空"); } if (StringUtils.isEmpty(pswd)) { return Json.fail(oper, "密碼不能爲空"); } pswd = MD5Utils.encrypt(pswd);// 密碼MD5加密 UsernamePasswordToken token = new UsernamePasswordToken(nickname, pswd); try { // 登陸 Subject subject = SecurityUtils.getSubject(); // 從session取出用戶信息 if (subject != null) { subject.logout(); } subject.login(token);// shiro 認證 this.iUserService.updateLoginTime(nickname);// 更新最近一次登陸時間 User user0 = (User) SecurityUtils.getSubject().getPrincipal(); //獲取用戶角色信息 List<Role> roles = new ArrayList<Role>(); List<Role> list = this.iRoleService.findUserRole(user.getNickname()); for (Role r : list) { roles.add(r); } //獲取用戶權限信息 List<Permission> perms = new ArrayList<Permission>(); List<Permission> list1 = this.iPermissionService.findRolePerm(user.getNickname()); for(Permission p : list1) { perms.add(p); } user0.setUserRoles(roles); user0.setUserPerms(perms); return Json.succ(oper).data(user0); } catch (UnknownAccountException uae) { log.warn("用戶賬號不正確"); return Json.fail(oper, "用戶賬號或密碼不正確"); } catch (IncorrectCredentialsException ice) { log.warn("用戶密碼不正確"); return Json.fail(oper, "用戶賬號或密碼不正確"); } catch (LockedAccountException lae) { log.warn("用戶賬號被鎖定"); return Json.fail(oper, "用戶賬號被鎖定不可用"); } catch (AuthenticationException ae) { log.warn("登陸出錯"); return Json.fail(oper, "登陸失敗:" + ae.getMessage()); } }