spring boot中比较简单的权限管理选择了使用shiro
然后用shiro-redis管理session,如下:
创建个shiroConfing,里面设置ShiroFilterFactoryBean------SecurityManager------myShiroRealm
然后在securityManager中设置缓存和session管理的方式如定义一个sessionManager指定用redis来操作session
然后开启shiro AOP注解支持。
创建自己的realm继承AuthorizingRealm做登陆跟权限的认证过程。
代码如下
@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFile(SecurityManager securityManager) { log.info("ShiroConfiguration.shirFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //拦截器 Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); //自定义拦截器 Map<String, Filter> filters = new LinkedHashMap<String, Filter>(); filters.put("authc", new MyFormAuthenticationFilter()); shiroFilterFactoryBean.setFilters(filters); // 配置不会被拦截的链接 顺序判断 filterChainDefinitionMap.put("/static/**", "anon");//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了; //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/**", "authc"); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/index"); //未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public MyShiroRealm myShiroRealm(){ MyShiroRealm myShiroRealm = new MyShiroRealm(); myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myShiroRealm; } /** * 凭证匹配器 * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5("")); return hashedCredentialsMatcher; } @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); // 自定义session管理 使用redis securityManager.setSessionManager(sessionManager()); // 自定义缓存实现 使用redis securityManager.setCacheManager(cacheManager()); return securityManager; } //自定义sessionManager @Bean public SessionManager sessionManager() { SessionConfig mySessionManager = new SessionConfig(); mySessionManager.setSessionDAO(redisSessionDAO()); return mySessionManager; } /** * 配置shiro redisManager * 使用的是shiro-redis开源插件 */ public RedisManager redisManager() { RedisManager redisManager = new RedisManager(); redisManager.setHost(host+":"+port); // redisManager.setExpire(18000);// 配置缓存过期时间 redisManager.setTimeout(timeout); redisManager.setPassword(password); return redisManager; } /** * cacheManager 缓存 redis实现 * 使用的是shiro-redis开源插件 */ @Bean public RedisCacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); redisCacheManager.setPrincipalIdFieldName("uid"); return redisCacheManager; } /** * RedisSessionDAO shiro sessionDao层的实现 通过redis * <p> * 使用的是shiro-redis开源插件 */ @Bean public RedisSessionDAO redisSessionDAO() { RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); return redisSessionDAO; } /** * 开启shiro aop注解支持. * 使用代理方式;所以需要开启代码支持; * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean(name="simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理 mappings.setProperty("UnauthorizedException","403"); r.setExceptionMappings(mappings); // None by default r.setDefaultErrorView("error"); // No default r.setExceptionAttribute("ex"); // Default is "exception" return r; } }
public class MyShiroRealm extends AuthorizingRealm {/** * 登陆认证的实现*/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//通过username从数据库中查找 User对象,如果找到,没找到. //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 UserInfo queryInfo = new UserInfo(); queryInfo.setUsername((String) token.getPrincipal());
//-------------------------------------------------------- List<SysRole> roleList = new ArrayList<>(); for (UserInfo info : userInfos) { List<SysRole> tmpRoleList = info.getRoleList(); for (SysRole sysRole : tmpRoleList) { sysRole.setPermissions(sysPermissionMapper.selectPermissionByRoleId(sysRole.getId())); roleList.add(sysRole); } } userInfo.setRoleList(roleList); SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( userInfo,//用户名 userInfo.getPassword(),//密码 ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//这里是密码+盐 getName()//realm name ); return authenticationInfo; } /** * 权限验证 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); UserInfo userInfo = (UserInfo) principalCollection.getPrimaryPrincipal(); for (SysRole sysRole : userInfo.getRoleList()) { authorizationInfo.addRole(sysRole.getRole()); for (SysPermission permission : sysRole.getPermissions()) { authorizationInfo.addStringPermission(permission.getPermission()); } } return authorizationInfo; } }
登陆Controller
@Controller public class LoginController {
@RequestMapping({"/", "/index"}) public String index() { System.out.println("goIndex"); return "user/index"; } @RequestMapping("/403") public String unauthorizedRole() { System.out.println("------没有权限-------"); return "403"; } @GetMapping("/login") public String goLoginPage(Model m) { System.out.print("goLogin"); Subject currentUser = SecurityUtils.getSubject(); if(!currentUser.isAuthenticated()){ return "login"; }else{ return "user/index"; } } @PostMapping("/login") public String login(@ModelAttribute UserInfo userinfo, HttpServletRequest request, Map<String, Object> map) {// 登录失败从request中获取shiro处理的异常信息。 // shiroLoginFailure:就是shiro异常类的全类名. Subject currentUser = SecurityUtils.getSubject();// 通过Subject对象的isAuthenticated判断该用户是否已经验证 if (!currentUser.isAuthenticated()) { // 如果该用户未验证,得到用户的账号和密码,使用UsernamePasswordToken的方式生成一个该用户的token凭证 UsernamePasswordToken token = new UsernamePasswordToken( userinfo.getUsername(), userinfo.getPassword()); // 开启记住我的功能,这里可以通过获取用户的提交的信息,判断是否选择记住我来决定开启或关闭 token.setRememberMe(true); String exception = (String) request.getAttribute("shiroLoginFailure"); System.out.println("exception=" + exception); String msg = ""; try { // 通过Subject对象的login来验证用户的身份 currentUser.login(token); log.info("login in sessionid:"+currentUser.getSession().getId().toString()); // 如果用户身份验证不通过会抛出相应的异常,可以通过抛出的异常来设置返回给前台的信息 } catch (UnknownAccountException uae) { log.info("===============账号不存在异常=>" + token.getPrincipal()); msg = "UnknownAccountException -- > 账号不存在:"; } catch (IncorrectCredentialsException ice) { log.info("===============密码错误异常=>" + token.getPrincipal()); msg = "IncorrectCredentialsException -- > 密码不正确:"; } catch (LockedAccountException lae) { log.info("===============账户被锁定异常=>" + token.getPrincipal()); msg = "kaptchaValidateFailed -- > 验证码错误"; } catch (AuthenticationException ae) { log.info("===============即验证不通过异常,为前面几个异常的父类=>" + token.getPrincipal()); msg = "else >> " + exception; } map.put("msg", msg); } // 此方法不处理登录成功,由shiro进行处理 return "login"; } }
使登陆成功的用户跳转倒index页
/** * 使登陆成功的用户跳转倒index */ public class MyFormAuthenticationFilter extends FormAuthenticationFilter { @Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { if (!StringUtils.isEmpty(getSuccessUrl())) { // getSession(false):如果当前session为null,则返回null,而不是创建一个新的session Session session = subject.getSession(false); if (session != null) { session.removeAttribute("shiroSavedRequest"); } } return super.onLoginSuccess(token, subject, request, response); // 或者下面这种方法 // String successUrl="/index"; // WebUtils.issueRedirect(request,response,successUrl); // System.out.println("登录首页拦截"); // return false } }
在Controller中用注解进行接口权限控制
@RequiresPermissions("student:add")
使用拦截器进行权限不足时返回的信息处理
@ControllerAdvice public class ExceptionHandleController { static final Logger log = LoggerFactory.getLogger(ExceptionHandleController.class); @ExceptionHandler(UnauthorizedException.class) public void handleShiroException(Exception ex,HttpServletRequest request, HttpServletResponse response) { log.info(ex.toString()); WebUtils.sendJson(response,false,"没有权限!"); //该方法返回json 如 {"success":true, "msg":"没有权限"} // return "redirect:/error/403"; } @ExceptionHandler(AuthorizationException.class) public void AuthorizationException(Exception ex) { log.info(ex.toString()); // return "redirect:/error/401"; } }