关闭

[笔记-架构探险]框架优化与功能扩展3.2.安全框架shiro、提供安全控制特性

标签: 框架安全插件架构shiro
845人阅读 评论(0) 收藏 举报
分类:

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();
    }

解释下核心调用流程:
这里写图片描述

  1. Application cod: 我们的项目代码
  2. Subject: 主体(我的理解是:登陆成功之后表示当前的用户,未成功之前顶多只能算是一个登录入口),它调用securityManager。

    主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

  3. SecurityManager : 安全管理器,是一个核心,所有的安全操作都由它来控制
    安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
  4. Realm:域,数据源,如上面的ini配置就是realm,还提供了其他的几种realm:
    1. PropertiesRealm
    2. JdbcRealm
    3. JndiLdapRealm
    4. 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.csdn.net/liujiahan629629/article/details/23128651(这个是大神的RBAC模型总结),我们使用最基础的模型来建立对应的表结构;
  1. user : 用户信息
  2. user_role : 用户和角色关联表
  3. role : 角色表
  4. role_permission : 角色和权限关联表
  5. permission : 权限表

* shiro.ini 配置 *
一年前自己在工作中遇到的一个shiro处理。下面的配置都能转换成纯Java配置类的。 不过还不太熟悉多种配置方式,就当知道有这么一回事。做个参考吧
http://blog.csdn.net/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&amp;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

配置说明

  1. authc.loginUrl : 认证时需要跳转的页面
  2. ds : 数据源配置信息
  3. jdbcRealm : 指定使用的类
  4. jdbcRealm.dataSource : 设置数据源
  5. jdbcRealm.authenticationQuery : 提供身份认证,通过username 查询 password
  6. jdbcRealm.userRolesQuery : 粗粒度校验,基于角色的授权验证,通过username 查询 role_name
  7. 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,看到这里,其实还还是没有太明白。怎么使用才能做到我们心中所想的,在哪里可以传递用户密码信息呢?怎么才能感觉到和我们用过滤器拦截的效果呢?。


提供安全控制特性

一般我们可能需要如下的一些需求:

  1. 登录界面,提供用户进行登录认证
  2. 未登录的用户,不能访问一些受保护的页面功能
  3. 登录过的用户,再次访问登录界面,将不会出现登录表单
  4. 不同角色的用户可以反问不同的操作,也就是”权限操作“

先来说说,我学习完这章节对shiro的理解:有两个大的概念:

  1. 认证:
    1. 通过配置,指定登录的页面或则登录action。指定哪些需要进行认证才能访问的路径
    2. 没有通过认证的将被强制跳转到登录页/方法(设置的)
    3. 提交表单进入登录的话,我们会把用户名和密码提交给框架,通过subject.login(token);
    4. 框架就会调用realm.doGetAuthenticationInfo方法,进行认证,也就是 我们需要把用户名对应的正确的密码返回给框架
    5. 框架会去调用匹配器,匹配,如果错误,则抛出异常
  2. 授权
    1. 经过了粗粒度的登录认证,和拦截。那么剩下的就是对每个用户进行拦截进行细粒度的权限控制了
    2. 可以通过shiro提供的PathMatchingFilter过滤器进行对每种情况进行拦截,然后在过滤器中调用subject.isPermitted() 等判断权限,角色的方式进行权限判断。(PathMatchingFilter提供了很多子类粗略的看了下很强大的过滤器) 一年前写的全注解shiro这里有怎么设置过滤器的方式
    3. subject.isPermitted 会触发realm.doGetAuthorizationInfo方法进行获取对应的角色集合和权限集合(如果使用了缓存,则会根据缓存机制触发该方法)
    4. 还可以通过我们自己的aop进行对方法进行拦截,同样也依赖subject.isPermitted等方法进行判断

最后总结一个简单的理解:提交用户名密码进行登录,每次访问都会根据认证的信息进行拦截判断,只有在登录之后才会放行,这个时候就要我们对权限进行控制。还有一个就是shiro抽象出来了3个sql语句,不是说非要我们把我们的安全用户权限表等都建立成统一的。这不抽象出来之后,由使用者自己填充数据源和sql语句。框架调用接口执行就性了


插件的形式扩展 一

先来看一张结构图。
这里写图片描述

exception

  1. AuthcException : 包装异常,认证异常,当非法访问时抛出
  2. AuthzException : 包装异常,授权异常,当权限无效时抛出

helper

  1. 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&amp;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);
    }
}

客户端的使用

  1. 首先肯定是要依赖本插件的。
  2. 配置插件需要的一些数据
smart.framework.jdbc.driverClassName=com.mysql.jdbc.Driver
smart.framework.jdbc.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&amp;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("/");
    }

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:132098次
    • 积分:2070
    • 等级:
    • 排名:千里之外
    • 原创:81篇
    • 转载:27篇
    • 译文:0篇
    • 评论:50条
    关于博客
    • 本博客记录一些学习笔记和工作开发中遇到的一些问题
    • QQ:99299684
    相关博客和项目
    1. mrcode 博客
    2016.02-2016.10为了学习技术而写的一个多人使用的博客
    2. 微信SDK自用封装
    工作中封装了一部分用到的微信api,搭成了一个架子,扩展方便。
    3. GitBook
    最近发现Gitbook能更好的记录笔记,以后就转移到这里了
    4. GitBook 自开发插件
    在使用GitBook过程中,方便使用,模仿开发了一些插件。
    最新评论