- 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()); } }