文章目录
依赖
shiro自定义请求头token,实现权限过滤
废话不多说,直接上代码,如果不了解shiro的基本使用,建议先入门shiro
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
大概结构
ShiroAuthFilter 自定义过滤器
自定义过滤器,extends AuthenticatingFilter 重写过滤token方法
package com.spshiro.auth;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author 落叶飘零
* @version 1.0
* @description: 会在ShiroBeanConfig的配置类里装载该 token过滤器
* @date 2022/5/19 1:39 下午
*/
public class ShiroAuthFilter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
String token = getToken((HttpServletRequest) servletRequest);
if(StringUtils.hasText(token)){
//ShiroAuthToken是自己定义实现AuthenticationToken接口的token
return new ShiroAuthToken(token);
}
//executeLogin调用该方法token==null就抛异常,我们在onAccessDenied入口
//重写方法没有token就提前返回并携带原因
return null;
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return false;
}
/**
* 登录失败,重写方法返回失败原因
* @param token
* @param e
* @param request
* @param response
* @return false
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
//重写登录失败,把登录失败的原因返回给前端
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)request).getHeader("Origin"));
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
//返回401 new JsonResult.error(throwable.getMessage())
httpResponse.getWriter().print("401");
} catch (IOException e1) {
e1.printStackTrace();
}
return false;
}
/**
* 程序入口 检验token是否携带,没携带就返回提示没有token
* @param servletRequest
* @param servletResponse
* @return
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
//获取用户token
String token = getToken((HttpServletRequest) servletRequest);
//如果token不存在提前返回401
if(!StringUtils.hasText(token)){
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", ((HttpServletRequest)servletRequest).getHeader("Origin"));
//new JsonResult.error(invalid token)
httpResponse.getWriter().print("401");
return false;
}
//executeLogin方法会校验createToken是否返回了非null AuthenticationToken
//上面我们提前校验提前返回信息给前端
return executeLogin(servletRequest, servletResponse);
}
/**
* 方法获取用户的token
* @param request
* @return token
*/
public String getToken(HttpServletRequest request){
//获取请求头
String token = request.getHeader("token");
//没有请求头就从参数里拿
if (!StringUtils.hasText(token)){
token=request.getParameter("token");
}
return token;
}
}
ShiroAuthRealm
package com.spshiro.auth;
import com.spshiro.entity.SysUserEntity;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
/**
* @author 落叶飘零
* @version 1.0
* @description: TODO
* @date 2022/5/19 1:29 下午
*/
@Component
public class ShiroAuthRealm extends AuthorizingRealm {
/**
* 判断是否支持token的类型 ****important****
* 每一个Ream都有一个supports方法,用于检测是否支持此Token,默认的采用了return false
* @param token
* @return
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof ShiroAuthToken;
}
/**
* 授权 集合会与 @RequiresPermissions()声明的权限方法匹配,通常不调用权限的方法不会执行
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SysUserEntity user = (SysUserEntity) principalCollection.getPrimaryPrincipal();
System.out.println("授权方法检索权限:当前的用户="+user);
//权限集合
Set<String> permsSet=new HashSet<>();
//根据用户的信息查权限存入set
if("123".equals(user.getUserId())){
permsSet.add("sys:need");
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}
/**
* 认证,登录的时候会调用
* 调用时机
* Subject subject = SecurityUtils.getSubject();
* subject.login(token);
* 在自定义token过滤器的executeLogin方法也会调用到上面两行,所以也会执行到下面的doGetAuthenticationInfo,每次请求都会认证
* authenticationToken能强转成字符串,因为用的是自定义的String类型的token
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String token = (String) authenticationToken.getPrincipal();
//通过token到redis缓存里获取到对应用户user
SysUserEntity userEntity=new SysUserEntity();
if(token.equals("123")){
userEntity.setUsername("落叶飘零");
userEntity.setUserId("123");
userEntity.setPassword("123");
System.out.println("认证token");
}else{
throw new IncorrectCredentialsException("token失效");
}
//user为空 token失效
//throw new IncorrectCredentialsException("token失效,请重新登录");
//也可以增加ip校验
//HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
// String ip = IPUtils.getIpAddr(request);
// if (!StringUtils.equals(ip, user.getLoginIp())) {
// throw new IncorrectCredentialsException("非法使用token");
// }
//是否锁定
//userEntity参数会让 SysUserEntity user = (SysUserEntity) principalCollection.getPrimaryPrincipal();强转
//同时可以做到程序全局能直接获取当前用户的信息
//Subject subject = SecurityUtils.getSubject();
// Object principal = subject.getPrincipal();
//(SysUserEntity) principal;
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userEntity, token, getName());
return info;
}
}
ShiroAuthToken 自定义token
自定义token
package com.spshiro.auth;
import org.apache.shiro.authc.AuthenticationToken;
/**
* @author 落叶飘零
* @version 1.0
* @description: 自定义token,在自定义token过滤器里使用
* @date 2022/5/19 1:47 下午
*/
public class ShiroAuthToken implements AuthenticationToken {
private String token;
public ShiroAuthToken(String token){
this.token=token;
}
@Override
public String getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
ShiroBeanConfig
工厂及SecurityManager
package com.spshiro.config;
import com.spshiro.auth.ShiroAuthFilter;
import com.spshiro.auth.ShiroAuthRealm;
import org.apache.shiro.mgt.SecurityManager;
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.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author 落叶飘零
* @version 1.0
* @description: TODO
* @date 2022/5/19 1:25 下午
*/
@Configuration
public class ShiroBeanConfig {
@Bean("securityManager")
public SecurityManager securityManager(ShiroAuthRealm shiroAuthRealm){
//ShiroAuthRealm是自定义reaml
DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
manager.setRealm(shiroAuthRealm);
return manager;
}
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilter=new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
//装载自定义的token过滤器
Map<String, Filter> tokenFilterMap=new HashMap<>();
tokenFilterMap.put("oauth2",new ShiroAuthFilter());
shiroFilter.setFilters(tokenFilterMap);
//普通路径过滤规则
Map<String, String> filterMap = new LinkedHashMap<>();
//登录
filterMap.put("/sys/login", "anon");
//验证码
filterMap.put("/captcha.jpg", "anon");
// 注册用户
filterMap.put("/sys/user/reg", "anon");
//swagger
filterMap.put("/swagger-resources/**", "anon");
filterMap.put("/swagger/**", "anon");
filterMap.put("/v2/api-docs", "anon");
filterMap.put("/v2/api-docs-ext", "anon");
filterMap.put("/swagger-ui.html", "anon");
filterMap.put("/webjars/**", "anon");
filterMap.put("/doc.html", "anon");
filterMap.put("/druid/**", "anon");
// api接口放权
filterMap.put("/api/**", "anon");
// websocket 放权
filterMap.put("/ws/**", "anon");
//微信登录放权
filterMap.put("/auth/login", "anon");
filterMap.put("/auth/code", "anon");
//微信绑定用户放权
filterMap.put("/auth/bind", "anon");
//统一认证登录放权
filterMap.put("/auth/cas", "anon");
//注册、分享、邀请链接放权
filterMap.put("/register", "anon");
filterMap.put("/share", "anon");
filterMap.put("/invite", "anon");
//图片预览放权(主要用在内容中的图片)
filterMap.put("/file/image/**", "anon");
// 静态资源
filterMap.put("/static/**", "anon");
// 接收第三方系统消息放权
filterMap.put("/sys/message/push","anon");
//自定义的token过滤器拦截其他任何请求
filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
UserUtil 用户获取工具方法
全局获取当前user信息
package com.spshiro.util;
import com.spshiro.entity.SysUserEntity;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
/**
* @author 落叶飘零
* (SysUserEntity) principal在realm认证传入SysUserEntity类型数据可强转
* @version 1.0
* @description: TODO
* @date 2022/5/19 7:47 下午
*/
public class UserUtil {
public static SysUserEntity getCurrentUser() {
Subject subject = SecurityUtils.getSubject();
if (subject == null) {
return null;
}
Object principal = subject.getPrincipal();
if (principal == null) {
return null;
}
return (SysUserEntity) principal;
}
}
loginController
package com.spshiro.controller;
import com.spshiro.entity.SysUserEntity;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.DelegatingSubject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 落叶飘零
* @version 1.0
* @description: TODO
* @date 2022/5/19 7:52 下午
*/
@RestController
public class LoginController {
/**
* 登录不登录 ****不重要*****,只要缓存里有对应的token就行
* @param username
* @param password
* @return
*/
@GetMapping("/sys/login")
public String login(String username,String password){
// SysUserEntity userEntity=new SysUserEntity();
// userEntity.setUsername(username);
// userEntity.setPassword(password);
//自己判断收否登录成功
//是否禁用
//....
// if(成功){
// String token =创建token;
// //存入redis等缓存
// Cookie tokenCookie = new Cookie("token", token);
// tokenCookie.setPath("/");
// tokenCookie.setHttpOnly(false);
// response.addCookie(tokenCookie);
// return JsonResult(token,过期时间);
// }
return "error";
}
/**
* 第三方登录,
* @param username
* @param password
* @return
*/
@GetMapping("/home")
public String loginCas(String username,String password){
//从cas拿到用户信息
// 与上面同理创建token
return "ok";
}
}
测试controller
package com.spshiro.controller;
import com.spshiro.entity.SysUserEntity;
import com.spshiro.service.TestServiceImpl;
import com.spshiro.util.UserUtil;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 落叶飘零
* @version 1.0
* @description: TODO
* @date 2022/5/19 3:33 下午
*/
@RestController
public class TestRestController {
@Autowired
private TestServiceImpl testService;
@GetMapping("/share")
public String share(){
return "share";
}
@GetMapping("/needToken")
public String needToken(){
SysUserEntity currentUser = UserUtil.getCurrentUser();
System.out.println("needToken访问的用户="+currentUser);
testService.needPermission();
return "成功访问"+currentUser;
}
}
@Service
public class TestServiceImpl {
//标注权限,需要在realm授权集合里才能访问
@RequiresPermissions(value = {"sys:need"})
public String needPermission(){
return "permission ok";
}
}
测试
http://localhost:8080/needToken?token=123