一、前言
接上文 SpringBoot整合Shiro实现登录鉴权,我们仅仅实现了/user/login接口实现登录,但是其他接口要怎么延续这个登录状态呢。目前大部分场景都是前后端分离的,比如接口提供给APP或者VUE前端,这个时候我们用token的方式来认证。由于shiro是通过session来管理会话的,所以我们将shiro的session_id作为token返给前端或者客户端。大致流程就是先通过登录接口获取到token(即session_id),然后后面在filter里没有配置过滤的接口,即需要鉴权的接口都通过在header里用authToken带上token,后端通过获取authToken里的值来判断是否通过鉴权即可。话不多说,直接上代码
二、上代码
0.代码变更
new file: src/main/java/com/yx/shiro/SessionCheckFilter.java
new file: src/main/java/com/yx/shiro/ShiroSessionManager.java
modified: src/main/java/com/yx/controller/UserController.java
modified: src/main/java/com/yx/shiro/shiroConfig.java
1./com/yx/shiro/SessionCheckFilter.java
核心就是这个类,shiro判断token无效时就会回调
onAccessDenied()
这个方法,我们只需要在这里返回给客户端对应的json提示语即可。
@Component
public class SessionCheckFilter extends UserFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
String token = WebUtils.toHttp(request).getHeader(ShiroSessionManager.AUTHORIZATION);
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setCharacterEncoding("UTF-8");
String responseJson;
if (StringUtils.isEmpty(token)) {
responseJson = JSON.toJSONString(ApiResult.failure("请登录")); //这里需要引入fastjson
} else {
responseJson = JSON.toJSONString(ApiResult.failure("token失效"));
}
httpResponse.getWriter().print(responseJson);
httpResponse.getWriter().flush();
httpResponse.getWriter().close();
return false;
}
}
2./com/yx/shiro/ShiroSessionManager.java
自定义session规则,采用请求头authToken携带sessionId的方式
public class ShiroSessionManager extends DefaultWebSessionManager {
public static final String AUTHORIZATION = "authToken";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public ShiroSessionManager(){
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response){
String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if(!StringUtils.isEmpty(token)){
//如果请求头中有 authToken 则其值为sessionId
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,token);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TRUE);
return token;
}
return null;
}
}
3./com/yx/shiro/shiroConfig.java
这里特别强调下,
shiroFilterFactoryBean()
里配置Filter类时map里面key值必须要和下面的/**里的value对应上才能使用自定义的过滤器
//权限管理,配置主要是Realm的管理认证
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
securityManager.setSessionManager(new ShiroSessionManager()); //配置ShiroSessionManager
return securityManager;
}
//Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
SessionCheckFilter sessionCheckFilter = new SessionCheckFilter();
Map<String, Filter> cumstomfilterMap = new HashMap<>();
//注意:map里面key值必须要和下面的/**里的value对应上才能使用自定义的过滤器
cumstomfilterMap.put("authc", sessionCheckFilter);
Map<String, String> filterMap = new LinkedHashMap<>();
// 配置不会被拦截的url
filterMap.put("/user/login", "anon");
filterMap.put("/user/register", "anon");
filterMap.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(filterMap);
shiroFilter.setFilters(cumstomfilterMap);
return shiroFilter;
}
4./com/yx/controller/UserController.java
@RequestMapping(value = "/login", method = RequestMethod.POST)
public Object login(HttpServletResponse response,
@RequestParam(value = "username", required = true) String userName,
@RequestParam(value = "password", required = true) String password) {
//用户认证信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, password);
try {
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
return ApiResult.success(subject.getSession().getId()); //将token返回给前端
} catch (UnknownAccountException e) {
return ApiResult.failure("用户名不存在!");
} catch (AuthenticationException e) {
return ApiResult.failure("账号或密码错误!");
}
}
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public Object logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return ApiResult.success("退出成功");
}