认证流程

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的登录功能 设计思路的更多相关文章

  1. 基于Redis的短链接设计思路

    [Markdown阅读][1] 今天上班的时候收到一个需要短链接的需求,之前的做法都是使用了新浪的短链接API(https://api.weibo.com/2/short_url/shorten.js ...

  2. SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)

    首先在shiro配置类中注入rememberMe管理器 /** * cookie对象; * rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cooki ...

  3. 实现单点登录功能的思路以及kafka同步数据

    单点登录以及用户数据同步思路与方案 当公司业务分布于多个子系统时, 同一用户在A系统注册,即可在其他所有关联系统使用, 并支持登录A系统后,自动在其他系统登录,退出同理. 在A平台修改通用的用户数据( ...

  4. 基于权限安全框架Shiro的登录验证功能实现

    目前在企业级项目里做权限安全方面喜欢使用Apache开源的Shiro框架或者Spring框架的子框架Spring Security. Apache Shiro是一个强大且易用的Java安全框架,执行身 ...

  5. django BBS project login登录功能实现

    1.models from django.db import models # Create your models here. from django.contrib.auth.models imp ...

  6. PHP实现登录功能DEMO

    PHP实现登录的原理是什么呢?就是利用Session实现的,用户访问网站,系统会自动在服务器生成一个Session文件,这个Session可以用来存储用户的登录信息.好了,这是基本储备,我们下面来实现 ...

  7. 二十 Filter&自动登录功能

    Filter过滤器 过滤器,其实就是对客户端发出来的请求进行过滤,浏览器发出,然后服务器用Servelt处理.在中间就可以过滤,起到的是拦截的作用. 不仅仅作用于客户端请求,而且过滤服务器响应 作用: ...

  8. 设计基于HTML5的APP登录功能及安全调用接口的方式

    转自:http://blog.csdn.net/linlzk/article/details/45536065 最近发现群内大伙对用Hbuilder做的APP怎么做登录功能以及维护登录状态非常困惑,而 ...

  9. 设计基于HTML5的APP登录功能及安全调用接口的方式(原理篇)

    登录 保存密码 安全 加密 最近发现群内大伙对用Hbuilder做的APP怎么做登录功能以及维护登录状态非常困惑,而我前一段时间正好稍微研究了一下,所以把我知道的告诉大家,节约大家查找资料的时间. 你 ...

随机推荐

  1. linux的scp命令可以在linux服务器之间复制文件和目录

    scp是secure copy的简写,用于在Linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器,而且scp传输是加密的.可能会稍微影响一下速度.当你服务器 ...

  2. 转 无损转换Image为Icon

    不可取 var handle = bmp.GetHicon();    //得到图标句柄return Icon.FromHandle(handle); //通过句柄得到图标 可取 /// <su ...

  3. 从 ASP.NET Core 2.1 迁移到 2.2 踩坑总结

    官方迁移文档:https://docs.microsoft.com/zh-cn/aspnet/core/migration/21-to-22?view=aspnetcore-2.2&tabs= ...

  4. redo log和bin log

    讲redolog和binlog之前,先要讲一下一条mysql语句的执行过程. 1.client的写请求到达连接器,连接器负责管理连接.验证权限: 2.然后是分析器,负责复习语法,如果这条语句有执行过, ...

  5. Python——hashlib(加密模块)

    主要用于对字符串的加密,最常用的为MD5加密: import hashlib def get_md5(data): obj = hashlib.md5() obj.update(data.encode ...

  6. 每日一题-——LeetCode(111)二叉树的最小深度

    题目描述: 给定一个二叉树,找出其最小深度. 最小深度是从根节点到最近叶子节点的最短路径上的节点数量. 思路一: 把每一层的结点加入到队列,每一层i+1,到下一层时,把上一层在队列中的结点都弹出,按从 ...

  7. Bootstrap 基本模板

    <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8& ...

  8. 第七届蓝桥杯C/C++程序设计本科B组决赛 ——棋子换位(代码补全题)

    棋子换位 有n个棋子A,n个棋子B,在棋盘上排成一行.它们中间隔着一个空位,用“.”表示,比如: AAA.BBB 现在需要所有的A棋子和B棋子交换位置.移动棋子的规则是:1. A棋子只能往右边移动,B ...

  9. vue中watch的详细用法(转载)

    在vue中,使用watch来响应数据的变化.watch的用法大致有三种.下面代码是watch的一种简单的用法: <input type="text" v-model=&quo ...

  10. vue中读取excel中数据

    安装xlsx npm install xlsx --save-dev 安装好后在需要的页面 引入插件 import xlsx from 'xlsx' 调用 $('#uploadFile').chang ...