基于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怎么做登录功能以及维护登录状态非常困惑,而我前一段时间正好稍微研究了一下,所以把我知道的告诉大家,节约大家查找资料的时间. 你 ...
随机推荐
- github的pull request是指什么意思?有什么用处(转)
https://www.cnblogs.com/-walker/p/6093277.html
- 【ES6】数组的扩展
1.Array.from(): 将伪数组对象和遍历的对象转为真数组 如果一个对象的键都是正整数或者0,并且有 Length属性,那么这个对象很想数组,称它为伪数组. 伪数组: let obj = { ...
- 关于MQ的几件小事(五)如何保证消息按顺序执行
1.为什么要保证顺序 消息队列中的若干消息如果是对同一个数据进行操作,这些操作具有前后的关系,必须要按前后的顺序执行,否则就会造成数据异常.举例: 比如通过mysql binlog进行两个数据库的数据 ...
- springboot启动流程(目录)
springboot出现有段时间了,不过却一直没有怎么去更多地了解它.一方面是工作的原因,另一方面是原来觉得是否有这个必要,但要持续做java似乎最终逃不开要去了解它的命运.于是考虑花一段时间去学习一 ...
- mongodb 备份与还原操作
环境信息: 版本: mongodb 3.2 备份: mongodump 恢复: mongorestore 本文参考官方文档:https://docs.mongodb.com/manual/refere ...
- SR开启时LOG_MODE必须是normal
SR开启时LOG_MODE必须是normal 需要一个初始化备份,
- 关于#error
很简单的一个东西,但是感觉使用价值没有太大.实现了以下,结果如下: 执行到#error语句的时候直接停止编译,在下面输出设定好的错误信息. 来自为知笔记(Wiz)
- javascript动态添加html节点
之前一直没怎么接触需要动态添加和删除html节点的项目,这次项目中用到了,也学习了. 在一个<table id="tab">标签中添加一个<tr id=" ...
- Win10设置开机进入启动设置模块(进入安全模式等)
Win10设置开机进入启动设置模块(进入安全模式等) Win10系统要进入安全模式或其他启动模式选择时,需要在系统中做如下设置后,才可在开机的时候对模式进行选择,操作如下: 1.依次点选:win10设 ...
- 可能是东半球最好的 Curl 学习指南,强烈建议收藏!
本文首发于:微信公众号「运维之美」,公众号 ID:Hi-Linux. 「运维之美」是一个有情怀.有态度,专注于 Linux 运维相关技术文章分享的公众号.公众号致力于为广大运维工作者分享各类技术文章和 ...