基于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怎么做登录功能以及维护登录状态非常困惑,而我前一段时间正好稍微研究了一下,所以把我知道的告诉大家,节约大家查找资料的时间. 你 ...
随机推荐
- java 禁用科学计数法
禁用科学计数法 Double num = 80000000000.000001; System.out.println("默认计数法:num=" + num); NumberFor ...
- 在ubuntu 16.04 的vm中添加新网卡,同一网段不同ip
在ubuntu 16.04 的vm中添加新网卡,同一网段不同ip 来源 https://blog.51cto.com/744478/2083672 在ubuntu 16.04 的vm中新加了一块网卡, ...
- Go part 7 反射,反射类型对象,反射值对象
反射 反射是指在程序运行期间对程序本身进行访问和修改的能力,(程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分,在运行程序时,程序无法获取自身的信息) 支持反射的语言可以在程序编 ...
- SPI简述
SPI是器件的比较常用的通信协议. SPI总共有四根线: SS:片选线,每个设备都和主机MCU有一条单独片选线相连,片选线拉低意味主机输出,也就是说一个主机可以和多个从机相连,只需要有足够多的片选线. ...
- shell 三剑客之 sed pattern 详解
sed 基础介绍 语法格式 sed 处理过程 sed 选项 cat sed.txt '-p' 打印输出 ,默认输出两次,流输出一次,源文件输出一次 sed 'p' sed.txt -n 只显示处理的 ...
- Android小经验:启动Eclipse,出现提示“......发现了以元素'd:skin'开头的无效内容。此处不应含有子元素...”
如图所示: 解决办法: 进入sdk目录下,把D:\android-sdks\system-images\android-22\android-wear\armeabi-v7a\devices.xml和 ...
- Android笔记(十五) Android中的基本组件——单选框和复选框
单选框和多选框通常用来在设置用户个人资料时候,选择性别.爱好等,不需要用户直接输入,直接在备选选项中选择,简单方便. 直接看代码: <?xml version="1.0" e ...
- python 中 ModuleNotFoundError: No module named 'Crypto' 错误处理
今天在微信小程序服务端集成了微信的登录解密模块 WXBizDataCrypt,集成后运行程序时出现了下面的错误 (.venv) [1lin24@1lin24]# python manager_dev. ...
- grafana忘记登陆密码
找到grafana的数据文件grafana.db find / -name "grafana.db" ps:默认的安装路径为/var/lib/grafana/grafana.db ...
- ECU 自动化生产测试系统
概述 ECU(Electronic Control Unit) 是现代车辆中最重要的部件之一,其稳定性.可靠性对车辆安全性的影响至关重要.如何保证ECU 生产质量和效率.如何在生产过程中对ECU 进行 ...