http://git.oschina.net/zhuqiang/smart-framework 跟着书上学习的 框架git地址
http://git.oschina.net/zhuqiang/mrweb 依赖上面框架的demo练习
https://git.oschina.net/zhuqiang/smart-plugin-security.git 本章所学习的插件形式的shiro框架封装
这章我觉得能把shiro学会使用了,一些流程也都更清楚了。还学会怎么封装使用shiro
开涛大哥的教程比较不错。一年前自己那个时候第一次接触shiro百度到处找教程,没有找到一个符合当初那个项目要求的demo。最后也还是使用了起来。没有什么理解。今天看完章节,虽然说还是只能理解一点皮毛。但是能更加清楚的了解这个shiro框架的一个流程机制。
http://jinnianshilongnian.iteye.com/blog/2018936【开涛讲shiro更详细】。 我就直接开始代码和总结了。
hello shiro
<!-- 安全框架 --
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
shiro.ini
[users]
admin=123456
shiro=1234
public static void main(String[] args) {
//初始化 securityManager
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager sm = factory.getInstance();
SecurityUtils.setSecurityManager(sm); //给这个工具类设置 securityManager,以后使用这个工具类的时候,就等于在操作securityManager相关的数据了
Subject subject = SecurityUtils.getSubject(); //获取当前用户(其实是获取到整个配置文件里面所有的用户信息)
UsernamePasswordToken token = new UsernamePasswordToken("shiro", "1234"); //登录信息
try {
subject.login(token); //使用登录信息和数据源中的信息比对(his.securityManager.login(this, token);)
}catch (AuthenticationException e){
System.out.println("登录失败:" + e);
return;
}
System.out.println("登录成功:" + subject.getPrincipal());
subject.logout();
}
解释下核心调用流程:
- Application cod: 我们的项目代码
- Subject: 主体(我的理解是:登陆成功之后表示当前的用户,未成功之前顶多只能算是一个登录入口),它调用securityManager。
主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
- SecurityManager : 安全管理器,是一个核心,所有的安全操作都由它来控制
安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器; - Realm:域,数据源,如上面的ini配置就是realm,还提供了其他的几种realm:
- PropertiesRealm
- JdbcRealm
- JndiLdapRealm
- ActiveDirectoryReam
域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
在web开发中使用 shiro
pom.xml
<!-- 对web的支持-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.4</version>
</dependency>
web.xml
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
通过listener初始化SecutityManager,并通过ShiroFilter来完成认证与授权。
配置realm
这里使用 jdbcRealm ,就要建立表。使用RBAC(0)模型 http://blog.youkuaiyun.com/liujiahan629629/article/details/23128651(这个是大神的RBAC模型总结),我们使用最基础的模型来建立对应的表结构;- user : 用户信息
- user_role : 用户和角色关联表
- role : 角色表
- role_permission : 角色和权限关联表
- permission : 权限表
* shiro.ini 配置 *
一年前自己在工作中遇到的一个shiro处理。下面的配置都能转换成纯Java配置类的。 不过还不太熟悉多种配置方式,就当知道有这么一回事。做个参考吧
http://blog.youkuaiyun.com/mr_zhuqiang/article/details/44427875
[main]
authc.loginUrl=/login
ds=org.apache.commons.dbcp.BasicDataSource
ds.driverClassName=com.mysql.jdbc.Driver
ds.url=jdbc:mysql://localhost:3306/study?useUnicode=true&characterEncoding=utf-8
ds.username=root
ds.password=123
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource=$ds
jdbcRealm.authenticationQuery=SELECT `password` FROM `user` WHERE username = ?
jdbcRealm.userRolesQuery=SELECT r.role_name FROM `user` u,user_role ur,role r WHERE u.id = ur.user_id AND ur.role_id = r.id AND u.username=?
jdbcRealm.permissionsQuery=SELECT p.permission_name FROM role r,role_permission rp,permission p WHERE r.id = rp.role_id AND rp.permission_id = p.permission_name AND r.role_name = ?
jdbcRealm.permissionsLookupEnabled=true
securityManager.realms=$jdbcRealm
[urls]
/=anon
/space/**=authc
配置说明
- authc.loginUrl : 认证时需要跳转的页面
- ds : 数据源配置信息
- jdbcRealm : 指定使用的类
- jdbcRealm.dataSource : 设置数据源
- jdbcRealm.authenticationQuery : 提供身份认证,通过username 查询 password
- jdbcRealm.userRolesQuery : 粗粒度校验,基于角色的授权验证,通过username 查询 role_name
- jdbcRealm.permissionsQuery : 细力度校验,基于权限授权验证,通过role_name 查询 permission_name 。同时需要开启permissionsLookupEnabled 属性,该设置才能被调用。
[urls]
/=anon: 这里是对/请求首页放行。
/space/**=authc: 这里/space/请求的路径进行认证处理
权限之间的设计技巧: 在permissions表中存放了所有的权限名,实际上是一个权限字符串,推荐使用“资源:操作”这种格式来命名,例如:product:view(查看产品权限)
默认的过滤器
过滤器名称 | 功能 | 配置项(极其默认值) |
---|---|---|
anon | 确保只有未登录(匿名)的用户发送的请求才能通过。 | - |
authc | 确保只已认证的用户发送的请求才能通过(未认证的,则跳转到登录页面) | (这些配置项其实没有看懂是什么意思)authc.loginUrl=/login.jsp authc.successUrl=/authc.usernameParam= usernameauthc.passwordParam=passwordauthc.rememberMeParam= emembermeauthc.failureKeyAttribute=shiroLoginFailure |
authcBasic | 提供BasicHTTP认证功能(在浏览器中弹出一个登录对话框) | authcBasic.applicationName=application |
logout | 接收结束会话的请求 | logout.redirectUrl=/ |
noSessionCreation | 提供No Session 解决方案(若有Session就会报错) | - |
perms | 确保只拥有特定权限的用户发送的请求才能通过 | - |
port | 确保只有特定端口的请求才能通过 | port=80 |
rest | 其提供REST解决方案(根据REST URL计算权限字符串) | - |
roles | 确保只有拥有特定角色的用户发送的请求才能通过 | - |
ssl | 确保只有HTTPS的请求才能通过 | - |
user | 确保只有已登录的用户发送的请求才能通过(包括已认证或已记住) | - |
jsp标签
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
<html>
<head>
<title>首页</title>
</head>
<body>
<shiro:guest>
<p>身份:游客</p>
<a href="<c:url value='/login' />">登录</a>
<a href="<c:url value='/register' />">注册</a>
</shiro:guest>
<shiro:user>
<p>身份:<shiro:principal/></p>
<a href="<c:url value='/customer' />">查询客户列表</a>
<a href="<c:url value='/register' />">退出</a>
</shiro:user>
</body>
</html>
标签 | 功能 |
---|---|
<shiro:guest> | 判断当前用户是否为游客 |
<shiro:user> | 判断当前用户是否已登录(包括:已记住的) |
<shiro:authenticated> | 判断当前用户是否已认证通过(不包括已记住) |
<shiro:notAuthenticated> | 判断当前用户是否未认证通过 |
<shiro:principal/> | 获取当前用户的相关信息,例如:用户名 |
<shiro:hasRole name="foo"> | 判断当前用户是否具有某种角色 |
<shiro:lacksRole name="foo"> | 判断当前用户是否缺少某种角色 |
<shiro:hasAnyRoles name="foo,bar"> | 判断当前用户是否具有指定中的任意一种角色 |
<shiro:hasPermission name="foo"> | 判断当前用户是否具有某种权限 |
<shiro:lacksPermission name="foo"> | 判断当前用户是否缺少某种权限 |
– | 以下为jsp标签,暂时还不知道shiro是否提供,如果不提供可以扩展 |
<shiro:hasAllRoles name="foo,bar"> | 判断当前用户是否同时具有每种角色 |
<shiro:hasAnyPermission name="foo,bar"> | 判断当前用户是否具有任意一种权限(foo 或bar) |
<shiro:hasAllPermission name="foo,bar"> | 判断当前用户是否同时具有指定权限(foo和bar) |
Java注解
注解 | 功能 |
---|---|
RequiresGurs | 确保被标注的方法可被匿名用户访问 |
RequiresUser | 只能被已登录的用户访问(包括:已认证或已记住的) |
RequiresAuthentication | 只能被已认证的用户访问(不包括已记住的) |
RequiresRoles | 仅被指定的角色的用户访问 |
RequiresPermissions | 仅被指定的权限的用户访问 |
注意:使用注解的方式,特别是RequiresRoles和RequiresPermissions的时候,就很不方便了。相当于硬编码了,数据库中权限修改了还得去修改对应标注的方法。
缓存
使用jdbcRealm的话,每次认证都需要访问数据库,所以可以使用缓存来提高性能。配置方式如下:
[main]
cacheManager=org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager=$cacheManager
配置之后,shiro会在内存中使用一个map来缓存查询结果,也支持EhCache的扩展。
加密
[main]
passwordMatcher=org.apache.shiro.authc.credential.PasswordMatcher
jdbcRealm.credentialsMatcher=$passwordMatcher
shiro对加密解密提供了强大的支持。最简单的使用,需要确保在创建密码的时候使用对应的加密算法,
Shiro提供了PasswordService接口。使用如下:
DefaultPasswordService defaultPasswordService = new DefaultPasswordService();
String encryptPassword = defaultPasswordService.encryptPassword("123");
encryptPassword就是加密后的密码了。shiro可以集成EhCache、Spring、集成CAS等功能。
关于这个shiro,看到这里,其实还还是没有太明白。怎么使用才能做到我们心中所想的,在哪里可以传递用户密码信息呢?怎么才能感觉到和我们用过滤器拦截的效果呢?。
提供安全控制特性
一般我们可能需要如下的一些需求:
- 登录界面,提供用户进行登录认证
- 未登录的用户,不能访问一些受保护的页面功能
- 登录过的用户,再次访问登录界面,将不会出现登录表单
- 不同角色的用户可以反问不同的操作,也就是”权限操作“
先来说说,我学习完这章节对shiro的理解:有两个大的概念:
- 认证:
- 通过配置,指定登录的页面或则登录action。指定哪些需要进行认证才能访问的路径
- 没有通过认证的将被强制跳转到登录页/方法(设置的)
- 提交表单进入登录的话,我们会把用户名和密码提交给框架,通过subject.login(token);
- 框架就会调用realm.doGetAuthenticationInfo方法,进行认证,也就是 我们需要把用户名对应的正确的密码返回给框架
- 框架会去调用匹配器,匹配,如果错误,则抛出异常
- 授权
- 经过了粗粒度的登录认证,和拦截。那么剩下的就是对每个用户进行拦截进行细粒度的权限控制了
- 可以通过shiro提供的PathMatchingFilter过滤器进行对每种情况进行拦截,然后在过滤器中调用subject.isPermitted() 等判断权限,角色的方式进行权限判断。(PathMatchingFilter提供了很多子类粗略的看了下很强大的过滤器) 一年前写的全注解shiro这里有怎么设置过滤器的方式
- subject.isPermitted 会触发realm.doGetAuthorizationInfo方法进行获取对应的角色集合和权限集合(如果使用了缓存,则会根据缓存机制触发该方法)
- 还可以通过我们自己的aop进行对方法进行拦截,同样也依赖subject.isPermitted等方法进行判断
最后总结一个简单的理解:提交用户名密码进行登录,每次访问都会根据认证的信息进行拦截判断,只有在登录之后才会放行,这个时候就要我们对权限进行控制。还有一个就是shiro抽象出来了3个sql语句,不是说非要我们把我们的安全用户权限表等都建立成统一的。这不抽象出来之后,由使用者自己填充数据源和sql语句。框架调用接口执行就性了
插件的形式扩展 一
先来看一张结构图。
exception
- AuthcException : 包装异常,认证异常,当非法访问时抛出
- AuthzException : 包装异常,授权异常,当权限无效时抛出
helper
- SecurityHelper : 包装了shiro的login和注销等常用方法的工具类
realm
1. Md5CredentialsMatcher : 密码匹配器
2. SmartCustomRealm :自定义realm,认证/授权 的核心
3. SmartJdbcRealm :框架的jdbcRealm,用于把自己的业务sql设置的配置,设置给父类,直接使用框架弄好的realm。
* 比较核心的 *
1. SecurityConfig : 获取客户端配置的一些属性。
2. SecurityConstant : 内置框架的常量,用于记录特定的属性名称,让客户端配置值
3. SmartSecurity : 自定义的接口,用于配合自定义realm获取shiro框架固定的密码,角色集合,权限集合 的接口。
4. SmartSecurityFilter : 继承至shiroFilter,用于我们自己的框架加载的时候,配置shiro的基础配置,设置realm,配置缓存等。
5. SmartSecurityPlugin : 用于插件的入口,会设置加载默认的配置文件,和加载shiro的filter,完成启用shiro的功能。
6. META_INF/services/javax.servlet.ServletContainerInitializer: 里面配置了SmartSecurityPlugin类名称,该类就是ServletContainerInitializer实现类。作用就是,只要该插件jar包被引用,会随着tomcat的启动,加载该实现类。 达到了,只要引用了本插件jar包,就会启动,不用额外的手动配置启动。
7. smart-security.ini : shiro的原生配置,这个配置文件。我觉得应该放到客户端去配置的。目前就先这样实现吧。
META_INF/services/javax.servlet.ServletContainerInitializer**
cn.mrcode.smartPluginSecurity.SmartSecurityPlugin
客户端配置文件
smart.framework.jdbc.driverClassName=com.mysql.jdbc.Driver
smart.framework.jdbc.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8
smart.framework.jdbc.username=root
smart.framework.jdbc.password=root
## 项目基础包名 ##
smart.framework.app.base_package=cn.mrcode.mrweb
## jsp的基础路径 ##
smart.framework.app.jsp_path=/WEB-INF/view/
## 静态资源的基础路径 ##
smart.framework.app.asset_path=/asset/
# 设置realms,使用smart框架提供的两个实现realm #
smart.plugin.security.realms=jdbc,custom
# 为custom提供数据库操作的支持查询接口 #
smart.plugin.security.custom.class=cn.mrcode.mrweb.AppSecurity
smart.plugin.security.cache=true
# 为jdbcRealm提供数据源的支持。 #
smart.plugin.security.jdbc.authc_query=SELECT `password` FROM `user` WHERE username = ?
smart.plugin.security.jdbc.roles_query=SELECT r.role_name FROM `user` u,user_role ur,role r WHERE u.id = ur.user_id AND ur.role_id = r.id AND u.username=?
smart.plugin.security.jdbc.permissions_query=SELECT p.permission_name FROM role r,role_permission rp,permission p WHERE r.id = rp.role_id AND rp.permission_id = p.permission_name AND r.role_name = ?
smart-security.ini
[main]
authc.loginUrl=/login
[urls]
/ = anon
/customer/** = authc
SmartSecurityPlugin
/**
* @author zhuqiang
* @version V1.0
* @date 2015/11/16 20:04
*/
public class SmartSecurityPlugin implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
//设置初始化参数
servletContext.setInitParameter("shiroConfigLocations","classpath:smart-security.ini");//覆盖掉默认的shiro配置文件名称
servletContext.addListener(EnvironmentLoaderListener.class); //标准的shiro的启动配置项
FilterRegistration.Dynamic smartSecurityFilter = servletContext.addFilter("SmartSecurityFilter", SmartSecurityFilter.class);
smartSecurityFilter.addMappingForUrlPatterns(null,false,"/*"); //加载我们自己的filter。并设置拦截路径
}
}
SmartSecurityFilter
/**
* @author zhuqiang
* @version V1.0
* @date 2015/11/16 20:12
*/
public class SmartSecurityFilter extends ShiroFilter {
@Override
public void init() throws Exception {
super.init();
WebSecurityManager webSecurityManager = super.getSecurityManager();
setRealms(webSecurityManager); //
setCache(webSecurityManager); //设置缓存,降低数据库查询次数,降低i/o访问
}
/**
* 设置realm,可同时支持多个realm。并按照先后顺序用逗号分割
* 读取到客户端配置文件的realm项,然后判断调用我们实现好的 realm。
* @param webSecurityManager
*/
private void setRealms(WebSecurityManager webSecurityManager) {
String securityRealms = SecurityConfig.getRealms(); //读取配置项
if(securityRealms != null){
String[] securityRealmsArray = securityRealms.split(",");
Set<Realm> realms = new LinkedHashSet<Realm>();
for (String securityRealm : securityRealmsArray) {
if(securityRealm.equalsIgnoreCase(SecurityConstant.REALMS_JDBC)){
addJdbcRealm(realms);
}else if (securityRealm.equalsIgnoreCase(SecurityConstant.REALMS_CUSTOM)){
addCustomRealm(realms);
}
}
RealmSecurityManager manager = (RealmSecurityManager) webSecurityManager;
manager.setRealms(realms);
}
}
/**
* 添加自己实现的 customRealm
* @param realms
*/
private void addCustomRealm(Set<Realm> realms) {
SmartSecurity smartSecurity = SecurityConfig.getSmartSecurity();
SmartCustomRealm smartCustomRealm = new SmartCustomRealm(smartSecurity);
realms.add(smartCustomRealm);
}
/** 添加自己实现的 jdbcRealm **/
private void addJdbcRealm(Set<Realm> realms) {
SmartJdbcRealm smartJdbcRealm = new SmartJdbcRealm();
realms.add(smartJdbcRealm);
}
/** 添加缓存 */
private void setCache(WebSecurityManager webSecurityManager) {
if(SecurityConfig.isCache()){
CachingSecurityManager cachingSecurityManager = (CachingSecurityManager) webSecurityManager;
cachingSecurityManager.setCacheManager(new MemoryConstrainedCacheManager()); //设置基于内存的缓存
}
}
}
SmartSecurity
/**
* 可在应用中实现以下方法,或则在smart.properties中提供sql的配置
* <ul>
* <li>smart.plugin.security.jdbc.authc_query:根据用户名获取密码</li>
* <li>smart.plugin.security.jdbc.roles_query:根据用户名获取角色名集合</li>
* <li>smart.plugin.security.jdbc.permissions_query:根据角色名获取权限名集合</li>
* </ul>
* @author zhuqiang
* @version V1.0
* @date 2015/11/14 22:59
*/
public interface SmartSecurity {
/** 根据用户名获取密码 */
String getPassword(String username);
/** 根据用户名获取角色名称集合 **/
Set<String> getRoleNameSet(String username);
/** 根据用户名获取权限集合 **/
Set<String> getPermissionNameSet(String roleName);
}
SecurityConstant
/**
* @author zhuqiang
* @version V1.0
* @date 2015/11/16 21:35
*/
public interface SecurityConstant {
/** 配置 realms,用逗号隔开**/
String REALMS = "smart.plugin.security.realms";
/** 内置 的jdbcRealm实现**/
String REALMS_JDBC="jdbc";
/** 内置的 实现的custom */
String REALMS_CUSTOM="custom";
String SMART_SECURITY="smart.plugin.security.custom.class";
/** 提供身份认证,通过username 查询 password的数据 */
String JDBC_AUTHC_QUERY="smart.plugin.security.jdbc.authc_query";
/** 粗粒度校验,基于角色的授权验证,通过username 查询 role_name */
String JDBC_ROLES_QUERY="smart.plugin.security.jdbc.roles_query";
/** 细力度校验,基于权限授权验证,通过role_name 查询 */
String JDBC_PERMISSIONS_QUERY="smart.plugin.security.jdbc.permissions_query";
String CACHE="smart.plugin.security.cache";
}
SecurityConfig
**
* @author zhuqiang
* @version V1.0
* @date 2015/11/16 21:33
*/
public class SecurityConfig {
public static String getRealms(){
return ConfigHelper.getString(SecurityConstant.REALMS);
}
/**
* 加载 配置文件中,配置的jdbcRealm的辅助接口
* @return
*/
public static SmartSecurity getSmartSecurity() {
String className = ConfigHelper.getString(SecurityConstant.SMART_SECURITY);
return (SmartSecurity) ReflectionUtil.newInstance(className);
}
/**
* 获取配置项目,是否启用缓存
* @return
*/
public static boolean isCache() {
return ConfigHelper.getBoolean(SecurityConstant.CACHE);
}
public static String getAuthenticationQuery() {
return ConfigHelper.getString(SecurityConstant.JDBC_AUTHC_QUERY);
}
public static String getUserRolesQuery() {
return ConfigHelper.getString(SecurityConstant.JDBC_ROLES_QUERY);
}
public static String getPermissionsQuery() {
return ConfigHelper.getString(SecurityConstant.JDBC_PERMISSIONS_QUERY);
}
}
Md5CredentialsMatcher
/**
* @author zhuqiang
* @version V1.0
* @date 2015/11/16 21:15
*/
public class Md5CredentialsMatcher implements CredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
//未加密的密码
String submitted = String.valueOf(((UsernamePasswordToken) authenticationToken).getPassword());
//获取数据库中存储的密码,已通过MD5加密
//这里需要注意的是:使用 subject.login 的时候,会第一次调用这个匹配器,获取的加密密码是char[]类型的。
/*进入doGetAuthenticationInfo之后,由于我们使用用户名查询到数据库中的密码,并设置返回了。在这里set密码的时候,需要变成char类型
不然的话,在这里就会因为两次强转的类型不一致抛出异常。
*/
char[] arr = (char[])authenticationInfo.getCredentials();
String encrypted = String.valueOf(arr);
return DigestUtils.md5Hex(submitted).equalsIgnoreCase(encrypted); //提交的密码,和数据库查询出来的密码进行对比
}
}
SmartCustomRealm
/**
* @author zhuqiang
* @version V1.0
* @date 2015/11/16 20:55
*/
public class SmartCustomRealm extends AuthorizingRealm {
private final SmartSecurity smartSecurity;
public SmartCustomRealm(SmartSecurity smartSecurity) {
this.smartSecurity = smartSecurity;
super.setName(SecurityConstant.REALMS_CUSTOM);
super.setCredentialsMatcher(new Md5CredentialsMatcher()); //使用自己的md5加密算法
}
/**
* 用于认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
if (token == null) {
throw new AuthenticationException("参数 token 非法!");
}
//获取从表单中提交过来的用户名
String username = ((UsernamePasswordToken) token).getUsername();
//通过用户名获取数据库中存储的密码
String password = smartSecurity.getPassword(username);
//把用户名和密码放入AuthenticationInfo中以便后续验证操作
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo();
authenticationInfo.setPrincipals(new SimplePrincipalCollection(username, super.getName()));
authenticationInfo.setCredentials(password.toCharArray());
return authenticationInfo;
}
/**
* 授权
* @param principals
* @return
*/
@Override
public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
throw new AuthorizationException("参数 principals 非法!");
}
//获取已认证的用户名
String username = (String) super.getAvailablePrincipal(principals);
//获取该用户的权限名称集合
Set<String> roleNameSet = smartSecurity.getRoleNameSet(username);
//获取该用户所有的权限集合
Set<String> permNameSet = new HashSet<String>();
if (roleNameSet != null && roleNameSet.size() > 0) {
for (String roleName : roleNameSet) {
Set<String> currentPermNameSet = smartSecurity.getPermissionNameSet(roleName);
permNameSet.addAll(currentPermNameSet);
}
}
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roleNameSet);
authorizationInfo.setStringPermissions(permNameSet);
return authorizationInfo;
}
}
SmartJdbcRealm
/**
* @author zhuqiang
* @version V1.0
* @date 2015/11/16 21:10
*/
public class SmartJdbcRealm extends JdbcRealm {
public SmartJdbcRealm() {
super.setDataSource(DataBaseHelper.getDataSource());
super.setAuthenticationQuery(SecurityConfig.getAuthenticationQuery());
super.setUserRolesQuery(SecurityConfig.getUserRolesQuery());
super.setPermissionsQuery(SecurityConfig.getPermissionsQuery());
super.setPermissionsLookupEnabled(true);
super.setCredentialsMatcher(new Md5CredentialsMatcher());
}
}
SecurityHelper
/**
* @author zhuqiang
* @version V1.0
* @date 2015/11/14 23:34
*/
public class SecurityHelper {
/**
* 登录
* @param username
* @param password
* @throws AuthcException
*/
public static void login(String username,String password) throws AuthcException{
Subject subject = SecurityUtils.getSubject();
if(subject != null){
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try{
subject.login(token);
}catch (AuthenticationException e) {
throw new AuthcException(e);
}
}
}
/** 登出 **/
public static void logout(){
Subject subject = SecurityUtils.getSubject();
if(subject != null){
subject.logout();
}
}
}
两个异常
/**
* 授权异常,当权限无效时抛出
* @author zhuqiang
* @version V1.0
* @date 2015/11/14 23:27
*/
public class AuthzException extends RuntimeException{
public AuthzException(String message) {
super(message);
}
}
/**
* 认证异常,当非法访问时抛出
* @author zhuqiang
* @version V1.0
* @date 2015/11/14 23:26
*/
public class AuthcException extends Exception {
public AuthcException(Throwable cause) {
super(cause);
}
public AuthcException(String message) {
super(message);
}
}
客户端的使用
- 首先肯定是要依赖本插件的。
- 配置插件需要的一些数据
smart.framework.jdbc.driverClassName=com.mysql.jdbc.Driver
smart.framework.jdbc.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8
smart.framework.jdbc.username=root
smart.framework.jdbc.password=root
## 项目基础包名 ##
smart.framework.app.base_package=cn.mrcode.mrweb
## jsp的基础路径 ##
smart.framework.app.jsp_path=/WEB-INF/view/
## 静态资源的基础路径 ##
smart.framework.app.asset_path=/asset/
# 设置realms,使用smart框架提供的两个实现realm #
smart.plugin.security.realms=jdbc,custom
# 为custom提供数据库操作的支持查询接口 #
smart.plugin.security.custom.class=cn.mrcode.mrweb.AppSecurity
smart.plugin.security.cache=true
# 为jdbcRealm提供数据源的支持。 #
smart.plugin.security.jdbc.authc_query=SELECT `password` FROM `user` WHERE username = ?
smart.plugin.security.jdbc.roles_query=SELECT r.role_name FROM `user` u,user_role ur,role r WHERE u.id = ur.user_id AND ur.role_id = r.id AND u.username=?
smart.plugin.security.jdbc.permissions_query=SELECT p.permission_name FROM role r,role_permission rp,permission p WHERE r.id = rp.role_id AND rp.permission_id = p.permission_name AND r.role_name = ?
3.编写代码调用
@Action(value = "/login", method = "POST")
public View loginSubmit(Param param) {
String username = param.getString("username");
String password = param.getString("password");
try {
SecurityHelper.login(username, password); //获取到表单提交的用户密码委托给login方法,认证通过则不会抛出异常。认证通过后,shiro框架会自己处理认证的信息,是放入了session中。然后 用户访问的时候,会根据设置的拦截url在过滤器里面拦截
} catch (AuthcException e) {
LOGGER.error("login failure", e);
return new View("login.jsp");
}
return new View("/customer");
}
/** 注销 **/
@Action("/logout")
public View logout() {
SecurityHelper.logout();
return new View("/");
}