后台权限管理控制需基于用户-角色-资源关联关系限制,本文仅演示前后端分离情况下使用shiro作为权限管理工具
shiro配置文件
@Configuration
public class ShiroConfig {
@Bean("securityManager")
public SessionsSecurityManager securityManager(UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
securityManager.setRememberMeManager(null);
return securityManager;
}
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
//添加自定义的认证授权过滤器
Map<String, Filter> filters = new HashMap<>();
filters.put("auth", new AuthFilter());
shiroFilter.setFilters(filters);
//添加拦截路径
Map<String, String> filterMap = new LinkedHashMap<String, String>();
filterMap.put("/swagger/**", "anon");
filterMap.put("/v2/api-docs", "anon");
filterMap.put("/swagger-ui.html", "anon");
filterMap.put("/webjars/**", "anon");
filterMap.put("/swagger-resources/**", "anon");
filterMap.put("/statics/**", "anon");
filterMap.put("/login.html", "anon");
filterMap.put("/favicon.ico", "anon");
filterMap.put("/captcha.jpg", "anon");
filterMap.put("/sys/login", "anon");
filterMap.put("/sys/logout", "anon");
//除以上所有请求之外 均被自定义权限过滤拦截校验
filterMap.put("/**", "auth");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
/**
* 配置以下bean借助SpringAOP扫描使用Shiro注解的类
*
* */
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
自定义realm认证
@Component
public class UserRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysMenuService sysMenuService;
@Autowired
private RedisUtils redisUtils;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof AuthToken;
}
/**
* 认证(登录时调用)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException {
//获取前端传来的token
String accessToken = (String) authToken.getPrincipal();
String username = (String) redisUtils.get(RedisKey.SYS_USER+accessToken);
if (username == null)
throw new IncorrectCredentialsException("token失效,请重新登录");
SysUser user = sysUserService.findByUsername(username);
if (user == null)
throw new UnknownAccountException("用户不存在!");
if (user.getStatus() == 0)
throw new LockedAccountException("账号已被锁定,请联系管理员");
//传入user以及获取到的token实现自动认证
return new SimpleAuthenticationInfo(user, accessToken, getName());
}
/**
* 授权(验证权限时调用)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SysUser user = (SysUser) principals.getPrimaryPrincipal();
Long userId = user.getUserId();
//获取用户权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(getUserPermissions(userId));
return info;
}
//获取用户权限
private Set<String> getUserPermissions(long userId) {
List<String> permsList;
//管理员权限列表
if (userId == CommonConstant.SUPER_ADMIN) {
List<SysMenu> menuList = sysMenuService.selectAll();
permsList = new ArrayList<>(menuList.size());
for (SysMenu menu : menuList) {
permsList.add(menu.getPerms());
}
} else {
permsList = sysUserService.queryAllPerms(userId);
}
//用户权限列表
Set<String> permsSet = new HashSet<>();
for (String perms : permsList) {
if (StringUtils.isBlank(perms))
continue;
permsSet.addAll(Arrays.asList(perms.trim().split(",")));
}
return permsSet;
}
}
自定义权限过滤器
public class AuthFilter extends AuthenticatingFilter {
Gson gson = new Gson();
//生成自定义token
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
String token = getUserToken((HttpServletRequest) request);
return new AuthToken(token);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return ((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name());
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String token = getUserToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) {
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpServletResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.getWriter().write(gson.toJson(ResponseResult.error("请先登录")));
return false;
}
return executeLogin(request, response);
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpResponse.setCharacterEncoding("UTF-8");
try {
Throwable throwable = e.getCause() == null ? e : e.getCause();
httpResponse.getWriter().write(gson.toJson(gson.toJson(ResponseResult.error("登录凭证已失效,请重新登录"))));
} catch (IOException e1) {
e1.printStackTrace();
}
return false;
}
//如果header中不存在token,则从参数中获取token
private String getUserToken(HttpServletRequest request) {
String token = request.getHeader("token");
if (StringUtils.isBlank(token))
token = request.getParameter("token");
return token;
}
}
生成认证token
public class AuthToken implements AuthenticationToken {
private String token;
public AuthToken(String token) {
this.token = token;
}
@Override
public String getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
token生成工具类
public class TokenGenerator {
public static String generateValue() {
return generateValue(UUID.randomUUID().toString());
}
private static final char[] hexCode = "0123456789abcdef".toCharArray();
public static String toHexString(byte[] data) {
if(data == null) {
return null;
}
StringBuilder r = new StringBuilder(data.length*2);
for ( byte b : data) {
r.append(hexCode[(b >> 4) & 0xF]);
r.append(hexCode[(b & 0xF)]);
}
return r.toString();
}
public static String generateValue(String param) {
try {
MessageDigest algorithm = MessageDigest.getInstance("MD5");
algorithm.reset();
algorithm.update(param.getBytes());
byte[] messageDigest = algorithm.digest();
return toHexString(messageDigest);
} catch (Exception e) {
throw new ApiException("生成Token失败", e);
}
}
}
测试接口示例
//测试账户需先建立 角色-用户-资源 关联关系 该示例中用户拥有sys:menu:info权限
@PostMapping(value = "/login")
@ApiOperation(value = "登录", notes = "登录")
public Result login(@RequestBody LoginForm param) {
SysUser user = sysUserService.findByUsername(param.getUsername());
String password = param.getPassword();
if (user == null) {
return Result.error("账号错误");
}
if (!password.equals(user.getPassword())) {
return Result.error("密码错误");
}
String token = TokenGenerator.generateValue();
redisUtils.set(RedisKey.SYS_USER + token, param.getUsername(), 3600 * 8);
return Result.success(token);
}
}
@GetMapping(value = "/logout")
@ApiOperation(value = "退出", notes = "退出")
public String logout(@RequestParam("token") String token) {
redisUtils.del(RedisKey.SYS_USER + token);
return "redirect:login.html";
}
@GetMapping("/info")
@RequiresPermissions("sys:menu:info")
@ApiOperation(value = "测试用菜单信息", notes = "测试用菜单信息")
public Result info() {
return Result.success("恭喜你成功了··");
}