Shiro学习

一、什么是Shiro?

1. 概括定义

​ 一个强大的安全框架。执行身份认证、授权、密码学、会话管理。

2. 类似比较

​ Spring的安全框架,学习成本比Shiro高。但是一般简单的应用使用Shiro就够了。

3. 组成框架

​ Private Concerns:

​ Authentication:验证身份 (principal) credential (凭证)

​ Authorization:验证身份所具有的角色和权限

​ Session Management:将用户信息存储在会话中(JAVASE环境,WEB环境),以便用户下次登入时使用。

​ Cryptography:加密解密

​ Supporting Features:

​ Web Support Caching Concurrency Testing Run As Remember Me

二、认证流程

​ Subject (认证主体) .login(token)

​ |

​ Security Manager (安全管理器,分发并管理请求)

​ |

​ Authenticator (委托给认证器并调用认证策略) <-------- Authentication Strategy (策略)

​ |

​ Pluggable Realms (1 or more 源)

​ JDBC Realm

​ LDAP Realm

​ ActiveDirectory Realm

​ Custom Realm

三、使用说明

1. 添加依赖

<dependency>
	<groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
	<groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

2. 配置文件

​ 核心配置文件为.ini格式,可以分组的键值对形式。可以用 ;和 # 进行注释。

# define user info.
[users]
admin=123
tom=456

3. 基本用法

//用SecurityManager对指定的配置文件进行初始化
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
//绑定工具类
SecurityUtils.setSecurityManager(securityManager);
//获取主体
Subject subject = SecurityUtils.getSubject();
//登入
UsernamePasswordToken token = new UsernamePasswordToken("admin", "123");
try{
    subject.login(token);
} catch (UnkownAccountException e){
    logger.error("未知用户异常", e);
} catch (IncorrectCredentialsException e){
    logger.error("密码错误异常", e);
} catch (AuthenticationException e){
    logger.error("认证异常", e);
}
System.out.println("是否认证:" + subject.isAuthenticated());
//退出
subject.logout(token);
System.out.println("是否认证:" + subject.isAuthenticated());

4. 默认配置

# define user info.
[users]
admin=123
tom=456

# 以下配置可以不写则相当于以下默认配置......................................................
# 默认的securitymanager
securitymanager=org.apache.shiro.mgt.defaultsecuritymanager

# 默认的认证器
authenticator=org.apache.shiro.authc.pam.modularrealmauthenticator
# 将认证器添加到SecurityManager
securitymanager.authenticators=$authenticator

# 默认的认证策略
authenticationstrategy=org.apache.shiro.authc.pam.atleastonesuccessfulstrategy
# 将认证策略添加到认证器
authenticator.authenticationstrategy=$authenticationstrategy

# 默认的Realm
iniRealm=org.apache.shiro.realm.text.inirealm
# 将realm添加到SecurityManager, 多个则以逗号隔开
securitymanager.realms=$iniRealm

四、Realm

​ 默认有三种realm: IniRealm、JdbcRealm、PropertiesRealm

1. JdbcRealm

//用SecurityManager对指定的配置文件进行初始化
//Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//SecurityManager securityManager = factory.getInstance();
//绑定工具类
//SecurityUtils.setSecurityManager(securityManager);
//获取主体
//Subject subject = SecurityUtils.getSubject();
//上述代码抽取到ShiroUtils

Subject subject = ShiroUtils.getSubject("classpath:shiro/shiro.ini");
//登入
UsernamePasswordToken token = new UsernamePasswordToken("admin", "123");
try{
    subject.login(token);
} catch (UnkownAccountException e){
    logger.error("未知用户异常", e);
} catch (IncorrectCredentialsException e){
    logger.error("密码错误异常", e);
} catch (AuthenticationException e){
    logger.error("认证异常", e);
}
System.out.println("是否认证:" + subject.isAuthenticated());
//退出
subject.logout(token);
System.out.println("是否认证:" + subject.isAuthenticated());
#配置数据源
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
data.url=jdbc:mysql://localhost:3306/shiro
dataSource.username=root
#.ini语法 没有密码就不写,否则报错
#dataSource.password=

#使用JdbcRealm
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
#注入数据源
jdbcRealm.dataSource=$dataSource
#重写认证语句
jdbcRealm.authenticationQuery=select password from t_user where username = ?

#添加realm到SecurityManager
securityManager.realms=$jdbcRealm

解决日志显示问题:

<!-- 面向slfj的日志 -->
<dependency>
	<groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<!-- log4j实现 -->
<dependency>
	<groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<!-- slf4j整合log4j -->
<dependency>
	<groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>

2. 自定义Realm

​ 如果使用的是QQ,微信等登入,则需要自定义Realm。

public class MyRealm extends AuthorizingRealm {
    
    /**
     *	身份认证
     **/
    @Override
    protected AuthorizationInfo doGetAuthenticationInfo(AuthenticationToken token) throws
    AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token; 
        if(password == null){
            throw new UnkownAccountException("No account found for user [" + username + "]");
        }
        else if(!password.equals(token.getPassword())){
            throw new IncorrectCredentialsException("Error password for user [" + username + "]");
        }
        else {
            throw new AuthenticationException("Error Authentication for user [" + username + "]");
        }
    }
    
    /**
     * 授权
     **/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
        return null;
    }
}
#使用JdbcRealm
myRealm=com.demo.shiro.MyRealm
#添加realm到SecurityManager
securityManager.realms=$myRealm

五、整合spring

1. 添加依赖

<!-- 整合web项目 -->
<dependency>
	<groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
</dependency>
<!-- 整合spring -->
<dependency>
	<groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
</dependency>

2. 配置Shiro过滤器

<!-- 配置shiro过滤器,用来拦截所有请求,进行认证和授权 -->
<filter>
	<filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.delegatingFilterProxy</filter-class>
    <init-param>
    	<param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
	<filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

3. 定义ShiroBean

<!-- 配置密码匹配器 -->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    <property name="hashAlgorithmName" value="${algorithmName}"/>
    <property name="hashIterations" value="${hashIterations}"/>
    <property name="storedCredentialsHexEncoded" value="false"/>
</bean>
<!-- 配置ream -->
<bean id="realm" class="com.demo.shiro.JdbcSaltRealm">
    <!-- 注入DataSource -->
    <property name="dataSource" ref="dataSource"/>
    <!-- 注入密码匹配器 -->
    <property name="credentialsMatcher" ref="credentialsMatcher"/>
    <!-- 重写salt认证sql -->
    <property name="authenticationQuery">
    	<value>
            select password, login_name from t_user where login_name=?
        </value>
    </property>
</bean>
<!-- 配置SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realms">
    	<list>
        	<ref bean="ream"/>
        </list>
    </property>
</bean>
<!-- 定义一个名为shiroFilter的bean,用来配置url过滤规则 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <!-- 定义url过滤规则 -->
    ....
</bean

4.代码

//用于加密明文
public class PasswordServiceImpl implements PasswordService {
	@Value("${algorithmName}")
    private String algorithmName;
    @Value("${hashIterations}")
    private int hashIterations;
    
    @Override
    public String encrytPassword(Object passwordVo) throws IllegalArgumentException {
        PasswordVo vo = (PasswordVo) passwordVo;
        return new SimpleHash(algorithmName, vo.getPassword(), vo.getSalt(), hashIterations).toBase64();
  	}
    
    @Override
    public boolean passwordsMatch(Object submittedPlaintext, String encrypted) {
        return false;
    }
}
@RequestMapping("/login")
public String login(String loginName, String password, Model model, HttpSession session){
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken(loginName, password);
    try{
        subject.login(token);
        User user = (User) subject.getPrincipal();
        // subject.getSession() != session, but they have the same data.
        session.setAttribute("user", user);
        return "forward:/product/findAll";
    } catch (UnkownAccountException e){
        model.addAttribute("loginError", "用户名不存在");
    } catch (IncorrectCredentialsException e){
        model.addAttribute("loginError", "密码不正确");
    } catch (AuthenticationException e){
        model.addAttribute("loginError", "登入失败");
    }
    return "login";
}


@RequestMapping("/logout")
public String logout(){
    Subject subject = SecurityUtils.getSubject();
    subject.logout();
    return 
}

5. 实现登入保护

​ 有些url的访问需要登入,如果未登入,则跳转到登入页面。

​ 解决:配置url过滤规则

​ Shiro默认提供了11个过滤规则,常用的由:

​ 1) anon(AnonymousFilter) 不需要认证

​ 2) authc(FormAuthenticationFIlter) 需要登入认证

​ 3) role(RolesAuthenticationFilter) 需要角色认证

​ 4) perms(PermissionAuthorizationFilter) 需要权限认证

​ 其他不常用:

​ 5) authcBasic(BasicHttpAuthenticationFilter)

​ 6) logout(LogoutFilter)

​ 7) noSessionCreation(NoSessionCreationFilter)

​ 8) port(PortFilter) 需要指定的端口

​ 9) rest(HttpMethodPermissionFilter)

​ 10) ssl(SslFilter)

​ 11) user(UserFilter)

<!-- 定义一个名为shiroFilter的bean,用来配置url过滤规则 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <!-- 定义url过滤规则 -->
    <property name="filterChainDefinitions">
    	<value><!-- 由枚举类DefaultFilter提供规则 -->
        	/product/findAll=authc
            /teacher/**=authc, roles[teacher]
            /student/**=authc, roles[student]
        </value>
    </property>
    <!-- 重写默认的登入失败页面,默认/login.jsp -->
    <property name="loginUrl" value="/showLogin"/>
    <!-- 重写未授权页面,默认显示401 -->
    <property name="unAuthorizedUrlUrl" value="/401.jsp"/>
    
</bean

6. 角色和权限的验证

​ 1) url过滤规则中配置角色

​ 相同的url上配置多个角色需要自定义过滤器

public class CustomAuthorizationFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse, Object mappedValue)
        throws Exception {
        Subject subject = getSubject(request, response);
        String[] roleArray = (String[]) mappedValue;
        
        if(rolesArray == null || rolesArrays.length == 0) {
            return true;
        }
        
        //判断角色
        for(String role : rolesArrays) {
            if(subject.hasRole(rolesArray[i])) {
                return true;
            }
        }
        return false;
    }
}
<!-- 配置自定义过滤器 -->
<bean id="customRoles" class="com.demo.shiro.filter.CustomRolesAuthorizationFilter"/>
	    /product/findAll=authc
		/teacher/delete=authc,perms[teacher:delete]
        /teacher/**=authc, customRoles[teacher, manager]
        /student/**=authc, roles[student]

​ 2)重写角色sql

<!-- 配置ream -->
<bean id="realm" class="com.demo.shiro.JdbcSaltRealm">
    ...
    <!-- 重写角色sql -->
    <property name="userRolesQuery">
    	<value>
            select r.role_name from t_user u left join
            user_role ur on u.id= ur.user_id left join
            t_role r on ur.role_id = r.id where u.login_name = ?
        </value>
    </property>
    
    <!-- 开启权限查找 -->
    <property name="permissionsLookupEnabled" value="true"/>
    
    <!-- 重写权限sql -->
     <property name="permissionsQuery">
    	<value>
            select p.permission_name from t_role r left join
            role_permission rp on r.id = rp.role_id left join
            t_permission p on rp.permission_id = p.id where r.role_name = ?
        </value>
    </property>
</bean>

​ 3) 重写未授权页面

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
	...
    <!-- 重写未授权页面,默认显示401 -->
	<property name="unAuthorizedUrlUrl" value="/401.jsp"/>
</bean>

7. 自定义UserRealm

public class UserRealm extends AuthorizingRealm {
    /**
     * 认证
    **/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
         String username = (String) token getPrincipal();
        //mybaits query.
        User user = ...;
        if(user == null){
            throw new UnknownAccountException();
        }
        return new SimpleAuthenticationInfo(user,//user info
                                           user.getPassword(),//password
                                           ByteSource.Util.bytes(user.getSalt()),//salt
                                           getName()//realm name
        );
    }
    
    /**
     * 授权
    **/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        User user = principals.getPrimaryPrincipal();
        String username = user.getLoginName();
        //mybaits query.
        Set<String> roles = ..;
        Set<String> permissions = ..;
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addRoles(roles);
        authorizationInfo.addPermissions(permissions);
        return authorizationInfo;
    }
   
}
<!-- 配置UserRealm替换默认的JdbcRealm -->
<bean id="userRealm" class="com.demo.shiro.UserRealm">
  	<!-- 注入密码匹配器 -->
    <property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>

8. Shiro标签

<!-- 导入标签库 -->
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

<shiro:principal/> == ${sessionScope.user.loginName}
<shiro:hasAnyRoles name="teacher,manager">
	...
</shiro:hasAnyRoles>
<shiro:hasAnyRoles name="student">
	...
</shiro:hasAnyRoles>
<shiro:hasPermission name="teacher:delete">
    ...
</shiro:hasPermission>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值