SpringBoot2.x配置Shiro实现权限管理,根据URL鉴权
之前使用 Shiro 鉴权的时候,一直用的是注解,如 @RequiresPermissions() 和 @RequiresRoles(),这种方法不利于维护和动态修改,代码侵入性强。所以,为了解决这个问题,通常都会采用URL鉴权,当写一个拦截器,获取请求的URL,然后查询当前登录用户的权限列表,判断请求的URL是否在权限列表的URL内,如果在则放行,否则拦截。
之前介绍了SpringSecurity权限管理,根据请求URL鉴权 ,本文就介绍一下 Shiro 的实现。
一、数据表设计
这里截图贴出几张表核心字段和部分数据
1. 用户表
2. 角色表
3. 权限表
4. 用户和角色关联表
5. 角色和权限关联表
二、依赖版本
springboot 版本 2.1.7.RELEASE
添加 shiro 依赖
-
-
org.apache.shiro -
shiro-spring -
1.4.0
三、Shiro 相关配置
1.自定义 Realm
package com.liuyanzhao.sens.config.shiro;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Validator;
import com.liuyanzhao.sens.entity.Permission;
import com.liuyanzhao.sens.entity.Role;
import com.liuyanzhao.sens.entity.User;
import com.liuyanzhao.sens.service.PermissionService;
import com.liuyanzhao.sens.service.RoleService;
import com.liuyanzhao.sens.service.UserService;
import com.liuyanzhao.sens.utils.LocaleMessageUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.*;
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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/*** 默认的realm** @author 言曌* @date 2018/9/1 上午10:47*/
@Slf4j
public class MyRealm extends AuthorizingRealm {@Autowired@Lazyprivate UserService userService;@Autowired@Lazyprivate RoleService roleService;@Autowired@Lazyprivate PermissionService permissionService;/*** 认证信息(身份验证) Authentication 是用来验证用户身份*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {User user = userService.findByUserName(account);if (user == null) {return null;}//封装authenticationInfo,准备验证密码SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, // 用户名user.getUserPass(), // 密码ByteSource.Util.bytes("sens"), // 盐getName() // realm namereturn authenticationInfo;}@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();User user = (User) principals.getPrimaryPrincipal();List roles = roleService.listRolesByUserId(user.getId());for (Role role : roles) {authorizationInfo.addRole(role.getRole());List permissions = permissionService.listPermissionsByRoleId(role.getId());for (Permission p : permissions) {
authorizationInfo.addStringPermission(p.getPermission());
}}return authorizationInfo;}
}
注意:我们加密方式采用加盐(固定字符串 sens),md5十次
用户注册或添加用户或修改密码的时候
需要对用户密码加盐 sens,然后md5加密十次
可以使用 shiro 的 new Md5Hash(pwd, salt, i) 实现
如示例
user.setUserPass(new Md5Hash(password, "sens", 10).toString());
关于 permissionService 和 roleService 这里应该不用贴吧,大家应该能看懂吧
2. ShiroConfig
package com.liuyanzhao.sens.config.shiro;
import com.liuyanzhao.sens.config.properties.IgnoredUrlsProperties;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/*** @author 言曌* @date 2018/8/20 上午6:19*/
@Configuration
public class ShiroConfig {@BeanIgnoredUrlsProperties getIgnoredUrlsProperties() {return new IgnoredUrlsProperties();}@Beanpublic ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);//自定义拦截器Map filtersMap = new LinkedHashMap();//访问权限配置filtersMap.put("requestURL", getURLPathMatchingFilter());shiroFilterFactoryBean.setFilters(filtersMap);//拦截器.Map filterChainDefinitionMap = new LinkedHashMap();// 配置不会被拦截的链接 顺序判断List urls = getIgnoredUrlsProperties().getUrls();for (String url : urls) {filterChainDefinitionMap.put(url, "anon");}filterChainDefinitionMap.put("/admin", "requestURL");filterChainDefinitionMap.put("/admin/**", "requestURL");filterChainDefinitionMap.put("/**", "anon");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);// 如果不设置默认会自动寻找Web工程根目录下的"/login"页面shiroFilterFactoryBean.setLoginUrl("/admin/login");// 登录成功后要跳转的链接shiroFilterFactoryBean.setSuccessUrl("/");//未授权界面;shiroFilterFactoryBean.setUnauthorizedUrl("/403");return shiroFilterFactoryBean;}@Beanpublic SecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(myRealm());return securityManager;}/*** 需要密码登录的realm** @return MyShiroRealm*/@Beanpublic MyRealm myRealm() {MyRealm myRealm = new MyRealm();myRealm.setCredentialsMatcher(hashedCredentialsMatcher());return myRealm;}/*** 凭证匹配器* * 加密算法:md5加盐加密10次** @return*/@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();//散列算法:这里使用MD5算法;hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列的次数,md5("")hashedCredentialsMatcher.setHashIterations(10);return hashedCredentialsMatcher;}/*** 访问 权限 拦截器** @return*/public URLPathMatchingFilter getURLPathMatchingFilter() {return new URLPathMatchingFilter();}
}
之前我们都是用,authc 是 shiro 内部的,目前不满足我们的需求
filterChainDefinitionMap.put("/admin/**", "authc");
我们需要自己写一个根据 URL 过滤的拦截器,即 URLPathMatchingFilter 类
3. 自定义 URL 拦截器:URLPathMatchingFilter
package com.liuyanzhao.sens.config.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.Set;
/*** URL拦截器* @author 言曌* @date 2019-10-12 17:56*/
public class URLPathMatchingFilter extends PathMatchingFilter {@Overrideprotected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {//请求的urlString requestURL = getPathWithinApplication(request);System.out.println("请求的url :" + requestURL);Subject subject = SecurityUtils.getSubject();if (!subject.isAuthenticated()) {// 如果没有登录, 跳到登录页面WebUtils.issueRedirect(request, response, "/admin/login");return false;}//从session里读取当前用户的权限URL列表Set urls = (Set) subject.getSession().getAttribute("permissionUrls");if (urls.contains(requestURL)) {return true;}//没有权限,跳到403页面WebUtils.issueRedirect(request, response, "/403");return false;}
}
现在在 URL 拦截器里,从 session 里查询当前登录用户的权限URL列表,然后判断请求的URL是否在那个URL列表里就行。
(说明一下:登录成功的时候,我们会查询当前登录用户的权限列表,从里面获取URL列表,然后放到 Session 里。)
4. 将匿名访问的URL写到 application.yml 中
这里我们拦截 /admin/** 的页面
但是想要放行一些特殊的,如 /admin/login,/admin/register 这些是登录页面
之前我们都是直接写
filterChainDefinitionMap.put("/admin/login", "anno");
filterChainDefinitionMap.put("/admin/register", "anno");
但是一旦多起来比较麻烦,我们希望写在配置文件里
如下 application.yml
# 忽略鉴权url,即设置为anon的url ignored:urls:- /admin/login- /admin/getLogin- /admin/register- /admin/getRegister- /admin/forget- /admin/getForget
然后创建一个类 IgnoredUrlsProperties
package com.liuyanzhao.sens.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/*** @author liuyanzhao*/
@Data
@Configuration
@ConfigurationProperties(prefix = "ignored")
public class IgnoredUrlsProperties {private List urls = new ArrayList<>();
}
通过注入这个类就能获取 urls
但是在 ShiroConfig 里无法直接注入,如
/*** 无法注入,ignoredUrlsProperties为null* 需要改成 @Bean*/ @Autowired private IgnoredUrlsProperties ignoredUrlsProperties;
这样是不行的
需要改成 @Bean 这种,手动 new 一个
@Bean
IgnoredUrlsProperties getIgnoredUrlsProperties() {return new IgnoredUrlsProperties();
}
至此 shiro 的相关配置就结束了
下面介绍一下登录和登出
四、登录和登出
1.登录
/*** 验证登录信息** @param account 用户名* @param password password 密码* @return JsonResult JsonResult*/@PostMapping(value = "/getLogin")@ResponseBodypublic JsonResult getLogin(String account, String password) {Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken(account, password);try {subject.login(token);if (subject.isAuthenticated()) {User user = (User) subject.getPrincipal();// 将用户的权限URL列表放到 session 中Set permissionUrls = permissionService.findPermissionUrlsByUserId(user.getId());subject.getSession().setAttribute("permissionUrls", permissionUrls);return new JsonResult(200, "登录成功");}} catch (UnknownAccountException e) {log.info("UnknownAccountException -- > 账号不存在:");return new JsonResult(500, "账号不存在");} catch (IncorrectCredentialsException e) {return new JsonResult(500, "密码错误");}} catch (LockedAccountException e) {log.info("LockedAccountException -- > 账号被锁定");return new JsonResult(500, "账号被锁定");} catch (Exception e) {log.info(e.getMessage());}return new JsonResult(500, "服务器内部错误");}
2.登出
/*** 退出登录** @return 重定向到/admin/login*/
@GetMapping(value = "/logOut")
public String logOut() {Subject subject = SecurityUtils.getSubject();subject.logout();return "redirect:/admin/login";
}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
