前言

这次在处理一个小项目时用到了前后端分离,服务端使用springboot2.x。权限验证使用了Shiro。前后端分离首先需要解决的是跨域问题,POST接口跨域时会预发送一个OPTIONS请求,浏览器收到响应后会继续执行POST请求。 前后端分离后为了保持会话状态使用session持久化插件shiro-redis,持久化session可以持久化到关系型数据库,也可以持久化到非关系型数据库(主要是重写SessionDao)。Shiro已提供了SessionDao接口和抽象类。如果项目中用到Swagger的话,还需要把swagger相关url放行。

搭建依赖

<dependency>
<!--session持久化插件-->
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.3</version>
</dependency>
<dependency>
<!--spring shiro依赖-->
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>

Shiro权限配置

1、ShiroConfig。这里主要是shiro核心配置。比如SecurityManager、SessionManager、CacheManager。

public class ShiroConfig {

    @Value("${spring.redis.shiro.host}")
private String host;
@Value("${spring.redis.shiro.port}")
private int port;
@Value("${spring.redis.shiro.timeout}")
private int timeout;
@Value("${spring.redis.shiro.password}")
private String password; /**
* 权限规则配置
**/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
filters.put("authc", new MyFormAuthorizationFilter()); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //swagger资源不拦截
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger-resources/**/**", "anon");
filterChainDefinitionMap.put("/v2/api-docs", "anon");
filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");
filterChainDefinitionMap.put("/configuration/security", "anon");
filterChainDefinitionMap.put("/configuration/ui", "anon"); filterChainDefinitionMap.put("/login/ajaxLogin", "anon");
filterChainDefinitionMap.put("/login/unauth", "anon");
filterChainDefinitionMap.put("/login/logout", "anon");
filterChainDefinitionMap.put("/login/register","anon");
filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setLoginUrl("/login/unauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
} /**
* shiro安全管理器(权限验证核心配置)
**/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
securityManager.setSessionManager(sessionManager());
securityManager.setCacheManager(cacheManager()); return securityManager;
} /**
* 会话管理
**/
@Bean
public SessionManager sessionManager() {
MySessionManager sessionManager = new MySessionManager();
sessionManager.setSessionIdUrlRewritingEnabled(false); //取消登陆跳转URL后面的jsessionid参数
sessionManager.setSessionDAO(sessionDAO());
sessionManager.setGlobalSessionTimeout(-1);//不过期
return sessionManager;
} /**
* 使用的是shiro-redis开源插件 缓存依赖
**/
@Bean
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host+":"+port);
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
return redisManager;
} /**
* 使用的是shiro-redis开源插件 session持久化
**/
public RedisSessionDAO sessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
} /**
* 缓存管理
**/
@Bean
public CacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
} /**
* 权限管理
**/
@Bean
public MyShiroRealm myShiroRealm() { return new MyShiroRealm();
}
}

2、MyShiroRealm 用户身份验证、自定义权限。

public class MyShiroRealm extends AuthorizingRealm {

    private Logger logger= LoggerFactory.getLogger(MyShiroRealm.class);

    @Resource
UserDao userDao; @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("===================权限验证==================");
return null;
} @Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
User currentUser=userDao.findUser(token.getUsername());
if(null == currentUser){
throw new AuthenticationException("账户不存在");
} if(!currentUser.getPassword().equals(new String(token.getPassword()))){
throw new IncorrectCredentialsException("账户密码不正确");
} if(currentUser.getIsdel()==1){
throw new LockedAccountException("账户已冻结");
} Subject subject = SecurityUtils.getSubject(); BIUser biUser=new BIUser();
biUser.setUserId(currentUser.getUserId());
biUser.setOrgId(currentUser.getOrgid());
biUser.setUserName(currentUser.getUsername());
biUser.setPassword(currentUser.getPassword());
biUser.setSessionId(subject.getSession().getId().toString());
biUser.setIsdel(currentUser.getIsdel());
biUser.setCreateTime(currentUser.getCreatetime()); logger.info("======已授权"+biUser.toString()+"===="); return new SimpleAuthenticationInfo(biUser,biUser.getPassword(),biUser.getUserName());
}
}

3、MySessionManager。shiro权限验证是根据客户端Cookie中的JSESSIONID值来确定身份是否合格。前后端分离后这个地方需要处理。客户端调用服务端登陆接口,验证通过后返回给客户端一个token值(这里我放的是sessionid)。客户端保存token值,然后调用其他接口时把token值放在header中。对前端来说也就是放在ajax的headers参数中。

public class MySessionManager extends DefaultWebSessionManager {

    private static final String AUTHORIZATION = "Authorization";

    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

    public MySessionManager() {
} @Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//从前端ajax headers中获取这个参数用来判断授权
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if (StringUtils.hasLength(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中取值
return super.getSessionId(request, response);
} }
}

4、MyFormAuthorizationFilter。对于跨域的POST请求,浏览器发起POST请求前都会发送一个OPTIONS请求已确定服务器是否可用,OPTIONS请求通过后继续执行POST请求,而shiro自带的权限验证是无法处理OPTIONS请求的,所以这里需要重写isAccessAllowed方法。

public class MyFormAuthorizationFilter extends FormAuthenticationFilter {
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) {
HttpServletRequest httpServletRequest = WebUtils.toHttp(servletRequest);
if ("OPTIONS".equals(httpServletRequest.getMethod())) {
return true;
}
return super.isAccessAllowed(servletRequest, servletResponse, o);
} }

5、处理跨域

 @Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("PUT", "DELETE", "GET", "POST")
.allowedHeaders("*")
.exposedHeaders("access-control-allow-headers", "access-control-allow-methods", "access-control-allow" +
"-origin", "access-control-max-age", "X-Frame-Options","Authorization")
.allowCredentials(false).maxAge(3600); }

在前后端分离项目中使用SpringBoot集成Shiro的更多相关文章

  1. 分享我在前后端分离项目中Gitlab-CI的经验

    长话短说,今天分享我为前后端分离项目搭建Gitlab CI/CD流程的一些额外经验. Before Gitlab-ci是Gitlab提供的CI/CD特性,结合Gitlab简单友好的配置界面,能愉悦的在 ...

  2. Spring-Gateway与Spring-Security在前后端分离项目中的实践

    前言 网上貌似webflux这一套的SpringSecurity操作资料貌似很少. 自己研究了一波,记录下来做一点备忘,如果能帮到也在迷惑的人一点点,就更好了. 新项目是前后端分离的项目,前台vue, ...

  3. 超简单!asp.net core前后端分离项目使用gitlab-ci持续集成到IIS

    现在好多使用gitlab-ci的持续集成的教程,大部分都是发布到linux系统上的,但是目前还是有很大一部分企业使用的都是windows系统使用IIS在部署.NET应用程序.这里写一下如何使用gitl ...

  4. 《论vue在前后端分离项目中的实践之年终总结》

    我是2014年的时候开始了解知道的vue,当时vue还不太成熟,想用但是又怕自己hold不住,况且那时候vue还没有成熟的(路由.验证.ui组件)插件,社区也是不温不火的,再说也没有合适的机遇让我去项 ...

  5. 服务器上详细前后端分离项目搭建(springboot+vue)

    介绍:本文用的经典的前后端分离开源项目ruoyi Gitee链接地址:https://gitee.com/y_project/RuoYi 一.拉取项目: 利用Git把项目拉取到本地,也可以直接利用id ...

  6. 前后端分离项目中后台集成shiro需要注意的二三事

    1. 修改 Shiro 认证失败后默认重定向处理问题 a. 继承需要使用的 ShiroFilter,重载 onAccessDenied() 方法: @Override protected boolea ...

  7. 08 Django REST Framework 解决前后端分离项目中的跨域问题

    01-安装模块 pip install django-cors-headers 02-添加到INSTALL_APPS中 INSTALLED_APPS = ( ... 'corsheaders', .. ...

  8. Aspnet Mvc 前后端分离项目手记(二)关于token认证

    在前后端分离的项目中,首先我们要解决的问题就是身份认证 以往的时候,我们使用cookie+session,或者只用cookie来保持会话. 一,先来复习一下cookie和session 首先我们来复习 ...

  9. docker-compose 部署 Vue+SpringBoot 前后端分离项目

    一.前言 本文将通过docker-compose来部署前端Vue项目到Nginx中,和运行后端SpringBoot项目 服务器基本环境: CentOS7.3 Dokcer MySQL 二.docker ...

随机推荐

  1. hudson搭建经验总结(二)

    作者:朱金灿 来源:http://blog.csdn.net/clever101 继续部署hudson,发现从google上的一个开源工程上:http://code.google.com/p/huds ...

  2. 2-2 Consul注册注销流程

    铺垫,创建健康检查方法,Consul服务器隔一段时间请求一下webapi里的一个方法,如果这个方法没有问题,则证明这个webapi还在正常工作,这个webapi提供的服务就存在.如果方法没有返回,或者 ...

  3. Method and system for public-key-based secure authentication to distributed legacy applications

    A method, a system, an apparatus, and a computer program product are presented for an authentication ...

  4. IT该忍者神龟Oracle 树操作(select…start with…connect by…prior)

    oracle树查询的最重要的就是select-start with-connect by-prior语法了.依托于该语法.我们能够将一个表形结构的以树的顺序列出来. 在以下列述了oracle中树型查询 ...

  5. Oracle使用dblink连接SqlServer

    使用场景:当你需要从ORACLE数据库上访问另一台SqlServer数据库的数据时,Oracle提供了一个工具:gateways.通过这个工具,你可以创建dblink来连接sqlserver或其他不同 ...

  6. mac 端安装JAVA开发环境

    一.maven安装 下载地址   https://maven.apache.org/download.cgi 下载 apache-maven-3.5.2-bin-zip 将下载的文件放在某路径下 修改 ...

  7. MVC 直接或间接继承自ActionResult的类型

    •ViewResult:使用View()可以指定一个页面,也可以指定传递的模型对象,如果没有指定参数则表示返回与Action同名的页面 •ContentResult:使用Content(string ...

  8. WPF 简单打印

    <Window x:Class="_096基本打印.MainWindow"        xmlns="http://schemas.microsoft.com/w ...

  9. 使用MultiByteToWideChar转换UTF8为GBK(UTF8在Windows的代码页是CP_UTF8)

    两个使用的函数: 1,UTF8转化为Unicode,inline为了编译后更快运行,老用到了,返回字符串为了使用链式表达式 inline WCHAR  *UTF8ToUnicode(const cha ...

  10. Android零基础入门第1节:Android的前世今生

    原文:Android零基础入门第1节:Android的前世今生 现在网上有很多各色Android资料了,但相对来说还是比较零散,Android覆盖的范围极广,最近刚好有机会全部拉通整理一遍,也保存起来 ...