SpringBoot 整合Shiro 实现前后端分离
程序员文章站
2022-03-15 11:51:41
...
SpringBoot 整合Shiro 实现前后端分离
最近着手开发一个SpringBoot + Shiro 的后台框架, 设计到前后端分离,需要跨域请求,但是登陆成功之后再进行其他操作总是提示未登录 重定向跳转到unlogin页面(前后分离模式,重定向也要改成json返回,后续贴出代码)
1、修改登陆方法
登陆之后返回sessionId给前端
//获取subject对象
Subject subject = SecurityUtils.getSubject();
//封装用户数据
LoginAuthToken token = new LoginAuthToken(username, password,rememberMe,userType);
try {
//执行Shiro配置的拦截方法
subject.login(token);
//登录失败:用户名不存在
} catch (UnknownAccountException e) {
e.printStackTrace();
return new ResultVoFailure("用户名不存在");
//登录失败:密码错误
} catch (IncorrectCredentialsException e) {
return new ResultVoFailure("密码错误");
}
return new ResultVoSuccess("登录成功",subject.getSession().getId());
2、重写SessionManager对象
重写sessionManager对象处理session
package com.pengheng.config.shiro;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
/**
* Created by Palerock
*/
public class SessionManager extends DefaultWebSessionManager {
private static final String TOKEN= "token";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public SessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getParameter(TOKEN);
//如果请求信息中有 TOKEN 则其值为sessionId
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
3、修改ShiroConfig配置引入自定义sessionManger
package com.pengheng.config.shiro;
import com.pengheng.config.shiro.filter.MyFormAuthenticationFilter;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.mindrot.jbcrypt.BCrypt;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
/**
* rememberMe cookie加密的** 建议每个项目都不一样 默认AES算法 **长度(128 256 512 位)
*/
@Value("${application.cookie.cipherKey}")
private String cookieCipherKey;
@Value("${application.cookie.maxAge}")
private int cookieMaxAge;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Bean
public SimpleCookie rememberMeCookie() {
// 这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
// <!-- 记住我cookie生效时间30天 ,单位秒;-->
simpleCookie.setMaxAge(cookieMaxAge);
return simpleCookie;
}
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey(Base64.decode(cookieCipherKey));
return cookieRememberMeManager;
}
/**
* ShiroFilterFactoryBean Shiro过滤器,针对IP地址进行拦截是否需要对应权限
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager());
Map<String, Filter> filters = new HashMap<>();
//设置自定义Filter 返回json数据不跳转页面
filters.put("authc",new MyFormAuthenticationFilter());
shiroFilterFactoryBean.setFilters(filters);
// 设置需要拦截的路径
Map<String, String> filterChain = new HashMap<>();
// 设置登出拦截
filterChain.put("/logout", "anon");
filterChain.put("/uploadFile", "anon");
filterChain.put("/login", "anon");
filterChain.put("/file", "anon");
//后台管理自定义过滤器配置,验证是否是对应角色
filterChain.put("/system/**","authc,roles[admin]");
//门户网站自定义过滤器配置,验证是否是对应角色
// filterChain.put("/portal/**","portalFilter");
//APP管理自定义过滤器配置,验证是否是对应角色
// filterChain.put("/app/**","appFilter");
filterChain.put("/common/*", "anon");
filterChain.put("/images/kaptcha.jpg", "anon");
filterChain.put("/**/*.js", "anon");
filterChain.put("/**/*.html", "anon");
filterChain.put("/", "anon");
// 拦截所有方法
filterChain.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChain);
// 设置拦截返回跳转的路径
// 未登录跳转页面
shiroFilterFactoryBean.setLoginUrl("/unlogin");
//登录成功跳转页面
// shiroFilterFactoryBean.setSuccessUrl("/");
// 未授权跳转页面
shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
return shiroFilterFactoryBean;
}
/**
* DefaultWebSecurityManager 默认web安全管理器
*/
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager() {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
// 关联ream
defaultWebSecurityManager.setRealm(authorizingRealm());
//设置session manage
defaultWebSecurityManager.setSessionManager(sessionManager());
defaultWebSecurityManager.setCacheManager(cacheManager());
defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
return defaultWebSecurityManager;
}
@Bean
public DefaultWebSessionManager sessionManager() {
SessionManager sessionManager = new SessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
return redisManager;
}
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
@Bean
public AuthorizingRealm authorizingRealm() {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher((token, info) -> {
LoginAuthToken userToken = (LoginAuthToken) token;
// 要验证的明文密码
String plaintext = new String(userToken.getPassword());
// 数据库中的加密后的密文
String hashed = info.getCredentials().toString();
return BCrypt.checkpw(plaintext, hashed);
});
return userRealm;
}
/**
* AOP注入回调授权方法
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager());
return authorizationAttributeSourceAdvisor;
}
}
4、HTML模拟跨域请求代码
<html>
<head>
<title>测试</title>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
</head>
<body>
<div>
<button id="login">登录</button>
<button id="logout">登出</button>
<button id="get">获取</button>
<span id="token"></span>
</div>
<script>
$(function(){
$("#login").click(function(){
$.ajax({
url:'http://localhost:8080/login',
data:{userName:'zhangsan',password:'123456'},
success:function(data){
//登陆成功后 将token存储到浏览器
$("#token").html(data.data)
}
})
})
$("#logout").click(function(){
$.ajax({
url:'http://localhost:8080/logout',
//将登陆后返回的token返回给后台,设置请求头和请求参数都可以
data:{token:$("#token").html()},
success:function(data){
alert(data.code+"-----"+data.msg);
}
})
})
$("#get").click(function(){
$.ajax({
url:'http://localhost:8080/common/quartz/list',
data:{token:$("#token").html()},
success:function(data){
alert(data.code+"-----"+data.msg);
}
})
})
})
</script>
</body>
</html>
5、自定义验证shiro 验证过滤器解决未登录后重定向跳转问题
当用户未登录的情况下请求操作,默认shiro会重定向,前后端分离模式需要返回json对象,需要重写FormAuthenticationFilter 过滤器的onAccessDenied方法
package com.pengheng.config.shiro.filter;
import com.pengheng.model.ResultVo;
import com.pengheng.model.ResultVoFailure;
import com.pengheng.util.Toolkits;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
@Slf4j
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) {
if (isLoginSubmission(request, response)) {
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
return executeLogin(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
//allow them to see the login page ;)
return true;
}
} else {
if (log.isTraceEnabled()) {
log.trace("Attempting to access a path which requires authentication. Forwarding to the " +
"Authentication url [" + getLoginUrl() + "]");
}
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setStatus(200);
httpServletResponse.setContentType("application/json;charset=utf-8");
//设置跨域允许,不然会提示跨域问题
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
PrintWriter out = httpServletResponse.getWriter();
ResultVo resultVo = new ResultVoFailure("用户未登录");
out.println(Toolkits.toJson(resultVo));
out.flush();
out.close();
return false;
}
}
}
上一篇: java编程思想笔记(十五)I/O高级
下一篇: 江西赣州上线运行区块链服务大厅
推荐阅读
-
SpringBoot2.0 整合 Shiro 框架,实现用户权限管理
-
SpringBoot 2.0整合阿里云OSS,实现动静分离架构
-
vue+springboot前后端分离实现单点登录跨域问题解决方法
-
SpringBoot整合Shiro实现登录认证的方法
-
SpringBoot整合mybatis实现后端接口
-
shiro,基于springboot,基于前后端分离,从登录认证到鉴权,从入门到放弃
-
ajax + vue + springboot + Restful实现前后端分离项目
-
Springboot + Vue + shiro 实现前后端分离、权限控制
-
jwt,spring security ,feign,zuul,eureka 前后端分离 整合 实现 简单 权限管理系统 与 用户认证的实现
-
Springboot+Spring Security实现前后端分离登录认证及权限控制的示例代码