基于Shiro的登录功能 设计思路
认证流程
Shiro的认证流程可以看作是个“有窗户黑盒”,
整个流程都有框架控制,对外的入口只有subject.login(token);,这代表“黑盒”
流程中的每一个组件,都可以使用Spring IOC容器里的Bean来替换,以实现拓展化、个性化,这代表“有窗户”。
本示例的认证流程可以参考下图:

(黑色区域中的红箭头线只代表受关注组件的流程,而不是直接调用,实际上Shiro每个流程的组件都是互相解耦的)
黑色区域里的两块白色区域,就是两个自定义的类。
关键代码如下:
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
AccountPo po = accountMapper.getByUsername(username);
if (po == null) {
throw new UnknownAccountException("用户名或密码错误。");
}
if (!po.getEnable_flag()) {
throw new DisabledAccountException("该用户已被禁用。");
}
CredentialsDto credentials = new CredentialsDto(po.getPassword(), po.getSalt());
return new SimpleAuthenticationInfo(username, credentials, getName());
}
/**
* 密码匹配
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo info) {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String password = new String(token.getPassword());
CredentialsDto credentials = (CredentialsDto) info.getCredentials();
String salt = credentials.getSalt();
String passwordMd5 = DigestUtils.md5Hex(password + salt);
boolean result = equals(passwordMd5, credentials.getPasswordMd5());
if (!result) {
throw new IncorrectCredentialsException("用户名或密码错误。");
}
return true;
}
认证过滤
直接在spring-shiro.xml的shiroFilter.filterChainDefinitions中定义哪些URL需要认证才能访问并不利于维护,
更合适的方法可能是写一个读取器,告诉filterChainDefinitions哪些URL不需要认证即可,需要认证的写在最后
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/" />
<property name="unauthorizedUrl" value="/html/unauthorized.html" />
<property name="filterChainDefinitions">
<value>
#{filterChainDefinitionsLoader.anonUrls()}
/**=authc
</value>
</property>
</bean>
/**
* Shiro的FilterChainDefinitions配置加载器
*
* @author Deolin
*/
public class FilterChainDefinitionsLoader { @Autowired
private UnauthenticationMapper unauthenticationMapper; /**
* 从数据库`unauthorization`表中读取所有匿名URL
*
* @return FilterChainDefinitions内容片段
*/
public String anonUrls() {
StringBuilder sb = new StringBuilder(400);
List<String> urls = authenticationMapper.listUrls();
for (String url : urls) {
sb.append(url);
sb.append("=anon");
sb.append(CRLF);
}
return sb.toString();
} }
FilterChainDefinitionsLoader.anonUrls()会在项目启动时调用,读取到所有匿名权限URL,
拼接成字符串,返回给filterChainDefinitions
授权配置
采用基于角色的权限设计,一个权限对应一个URL。一个用户间接地拥有多个权限,间接地能够访问多个权限所一一对应的URL。
这样一来,权限会全部配置在spring-shiro.xml的shiroFilter.filterChainDefinitions中,例如:
/html/students-view.html=perms["students-view"]
/student/add=perms["studentAdd"]
/student/list=perms["studentList"]
红蓝部分都是可自定义的,项目中每个涉及到权限的URL,都应该定义,
并保存到数据库`permission`表的name字段和url字段。
即`permission`是张字典表,项目中有多少个涉及到权限的URL,表中就会有多少字段。
授权配置也需要读取器
<property name="filterChainDefinitions">
<value>
#{filterChainDefinitionsLoader.anonUrls()}
#{filterChainDefinitionsLoader.permsUrls()}
/**=authc
</value>
</property>
/**
* 从数据库`permission`表中读取所有权限
*
* @return FilterChainDefinitions内容片段
*/
public String permsUrls() {
StringBuilder sb = new StringBuilder(400);
List<PermissionPo> pos = permissionMapper.list();
for (PermissionPo po : pos) {
sb.append(po.getUrl());
sb.append("=perms[\"");
sb.append(po.getName());
sb.append("\"]");
sb.append(CRLF);
}
return sb.toString();
}
授权流程

关键代码如下:
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> roleNames = accountMapper.listRoleNames(username);
Set<String> permissionNames = accountMapper.listPermissionNames(username);
info.setRoles(roleNames);
info.setStringPermissions(permissionNames);
return info;
}
基于过滤器链的认证、权限检查
shiro的filterChainDefinitions是在项目启动是加载的,所以可以通过spel表达式,调用bean的方法,将各种字典表中的URL,拼接起来,返回给filterChainDefinitions,解释为“xxxxURL需要认证才能访问”“xxURL需要yyy的权限才能访问”。
这里需要事先设计好字典表。
自定义过滤器
shiro的自带过滤器,似乎无法处理AJAX请求,至少无法返回项目通用JSON数据结构,比如自带的org.apache.shiro.web.filter.authc.AuthenticationFilter类和org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter类(它们分别被过滤器链中的authc标识和perms标识所映射),它们各自的onAccessDenied方法,只用重定向处理,而没有返回JSON的处理,所以,需要专门写一些自定义过滤器,以及用于处理AJAX请求的工具类。
/**
* 为shiro的自定义Filter准备的工具类<br>
* <br>
* 用于判断请求是否是AJAX请求和输出JSON
*
* @author Deolin
*/
public class FilterUtil { public static boolean isAJAX(ServletRequest request) {
return "XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"));
} public static void out(ServletResponse response, RequestResult requestResult) {
ObjectMapper jackson = new ObjectMapper();
PrintWriter out = null;
try {
response.setCharacterEncoding("UTF-8");
out = response.getWriter();
out.println(jackson.writeValueAsString(requestResult));
} catch (Exception e) {} finally {
if (null != out) {
out.flush();
out.close();
}
}
} }
/**
* 自定义AccessControlFilter<br>
* <br>
* 这个类会在spring-shiro.xml注册为looseAuthc,作为filterChainDefinitions的一个自定义标记,未通过过滤的页面请求会重定向到登录页面,未通过过滤的AJAX请求会返回JSON
*
* @author Deolin
*/
public class LooseAuthcFilter extends AccessControlFilter { @Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception {
Subject subject = getSubject(request, response);
// “记住我”自动登录 或 输入用户名+密码手动登录
if (subject.isRemembered() || subject.isAuthenticated()) {
return true;
} else {
return false;
}
} // 如果isAccessAllowed返回false,则调用这个方法
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (FilterUtil.isAJAX(request)) {
// 返回JSON对象,前端根据resp.result=-2跳转到登录页面
FilterUtil.out(response, RequestResult.unauthenticate());
} else {
// 重定向到登录页面
saveRequestAndRedirectToLogin(request, response);
}
return false;
} }
项目下载
https://github.com/spldeolin/login-demo
基于Shiro的登录功能 设计思路的更多相关文章
- 基于Redis的短链接设计思路
[Markdown阅读][1] 今天上班的时候收到一个需要短链接的需求,之前的做法都是使用了新浪的短链接API(https://api.weibo.com/2/short_url/shorten.js ...
- SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器 /** * cookie对象; * rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cooki ...
- 实现单点登录功能的思路以及kafka同步数据
单点登录以及用户数据同步思路与方案 当公司业务分布于多个子系统时, 同一用户在A系统注册,即可在其他所有关联系统使用, 并支持登录A系统后,自动在其他系统登录,退出同理. 在A平台修改通用的用户数据( ...
- 基于权限安全框架Shiro的登录验证功能实现
目前在企业级项目里做权限安全方面喜欢使用Apache开源的Shiro框架或者Spring框架的子框架Spring Security. Apache Shiro是一个强大且易用的Java安全框架,执行身 ...
- django BBS project login登录功能实现
1.models from django.db import models # Create your models here. from django.contrib.auth.models imp ...
- PHP实现登录功能DEMO
PHP实现登录的原理是什么呢?就是利用Session实现的,用户访问网站,系统会自动在服务器生成一个Session文件,这个Session可以用来存储用户的登录信息.好了,这是基本储备,我们下面来实现 ...
- 二十 Filter&自动登录功能
Filter过滤器 过滤器,其实就是对客户端发出来的请求进行过滤,浏览器发出,然后服务器用Servelt处理.在中间就可以过滤,起到的是拦截的作用. 不仅仅作用于客户端请求,而且过滤服务器响应 作用: ...
- 设计基于HTML5的APP登录功能及安全调用接口的方式
转自:http://blog.csdn.net/linlzk/article/details/45536065 最近发现群内大伙对用Hbuilder做的APP怎么做登录功能以及维护登录状态非常困惑,而 ...
- 设计基于HTML5的APP登录功能及安全调用接口的方式(原理篇)
登录 保存密码 安全 加密 最近发现群内大伙对用Hbuilder做的APP怎么做登录功能以及维护登录状态非常困惑,而我前一段时间正好稍微研究了一下,所以把我知道的告诉大家,节约大家查找资料的时间. 你 ...
随机推荐
- sql 语句实现一串数字位数不足在左侧补0的技巧
https://www.cnblogs.com/mylydg/p/5725189.html 在日常使用sql做查询插入操作时,我们通常会用到用sql查询一串编号,这串编号由数字组成.为了统一美观,我们 ...
- javscript函数的运用
函数,一段能够自动完成某些功能的代码块,函数的出现,既解决了重复使用重一功能的需求,又可以避免代码的臃肿性. 使用函数有两个要求:必须调用后才可以执行;函数名不要和关键字以及系统函数相同; 函数主要有 ...
- C#中的struct(结构)为值类型,struct类型全接触
众所周知,struct类型和class类型非常相似,尤其是在C++中,class可以办到的事struct几乎都可以实现.在C#中struc类型依然存在,它的用处相对来说已经显得越来越不重要,这里主要讲 ...
- Django之简介
一.MVC模型与MTV模型 MVC模型就是Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,他们之间以一种插件式的.松耦合的方式连接在一起 ...
- java - day019 - 反射
网络程序,难点在线程 反射 reflect 实用 类对象 来执行反射操作 反射获得一个类的定义信息 反射创建对象 反射调用成员变量, 方法 方法 获得类对象的三种方式 A.class Class.fo ...
- 【开200数组解决二叉搜索树的建立、遍历】PAT-L3-016. 二叉搜索树的结构——不用链表来搞定二叉搜索树
L3-016. 二叉搜索树的结构 二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值:若它的右子树不空,则右子树上所有结点的值均大于它 ...
- Spring Boot源码探索——自动配置的内部实现
前面写了两篇文章 <Spring Boot自动配置的魔法是怎么实现的>和 <Spring Boot起步依赖:定制starter>,分别分析了Spring Boot的自动配置和起 ...
- 《少年先疯队》第七次作业:团队项目设计完善&编码
博文简要信息表: 项目 内容 软件工程 https://www.cnblogs.com/nwnu-daizh/ 本次实验链接地址 https://www.cnblogs.com/nwnu-daizh/ ...
- Android上执行python脚本-QPython
看书,发现android可以跑python. 尝试了一下. 首先需要在手机上安装python环境,通过安装apk实现,这个apk叫QPython,还有同类的比如SL4A. QPython的官网:htt ...
- vue-cli 创建项目不成功 原因为项目文件夹无node_modules文件 进行npm install不成功解决办法
不知道有没有童鞋出现过全局安装vue-cli是成功的,但是创建项目时命令行报了很多错误,如下 本来是需要按照提示依次切换到项目文件夹,再npm run dev 即可完成项目创建并启动的,但是又报了如下 ...