概念
认证:即登录
授权:即允许某种操作
会话:即保持已登录状态
与Spring Security的比较:Shiro 较简单灵活,可以脱离Spring,权限控制粒度较粗
层次关系
Shiro操作类(SecurityUtils)-> 安全管理器(Security Manager)、实体(Subject)
安全管理器 -> 认证器(Authenticator,即登录)、授权器(Authorizer)、会话管理器(Session Manager)、缓存管理器(Cache Manager)、Realm(领域)
Realm -> Matcher(密码加密)
Realm:安全管理器与数据存储之间的中介
会话管理 和 缓存管理,Spring Session 和 Spring-data-redis 有更好的实现
一般表结构
1、用户表users(username, password)
2、角色表user_roles(username, role_name),记录用户名 和 角色名的对应关系
3、权限表roles_permission(role_name, permission),记录角色名 和 权限名的关系
Shiro应用组成
1、配置Maven依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId> <!-- 整合Spring 和 Shiro,包含了shrio-core和shiro-web -->
<version>1.4.0</version>
</dependency>
2、自定义Realm
新建realm包,里面新建MyRealm类
public class MyRealm extends AuthorizingRealm {
{ // Matcher
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5"); // 确定算法
matcher.setHashIterations(1); // 确定加密次数
super.setCredentialsMatcher(matcher);
} // 初始化块
// 这个方法用于认证,即subject.login()时调用
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException { // UsernamePasswordToken 是 AuthenticationToken的子类,这里的入参就是 subject.login()传进来的token
String username = (String) authenticationToken.getPrincipal(); // 获取首要信息,即UsernamePasswordToken里的username
String password = getPasswordByUsername(username); // 从数据库里查询密码
if(password == null){
return null; // 返回null即登录失败
}
// 提交的password加密后 和 数据库里的password进行对比
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, "MyRealm");
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("salt_1")); // 加盐,算法为md5($salt.$pass)
return authenticationInfo;
}
// 这个方法用于授权,即获取用户的角色和权限
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = (String) principalCollection.getPrimaryPrincipal();
Set<String> roles = getRolesByUsername(username); // 查询角色
Set<String> permissions = getPermissionsByUsername(username); // 查询权限
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
private String getPasswordByUsername(String username) {
return "eafb8cb896588a487ed9679994d25284"; // 这里需要改成访问数据库
}
private Set<String> getRolesByUsername(String userName) { // 通过Spring的Service访问数据库
Set roleSet = new HashSet<String>();
roleSet.add("role_1");
roleSet.add("role_2");
return roleSet; // 每个需要授权的地方,都会来调用doGetAuthorizationInfo,因此角色和权限信息需要缓存,给Service加上缓存机制即可
}
private Set<String> getPermissionsByUsername(String username) { // 这里需要改成访问数据库
Set permissionSet = new HashSet<String>();
permissionSet.add("user:delete");
permissionSet.add("user:update");
return permissionSet;
}
}
3、认证与授权
// Security Manager
MyRealm realm = new MyRealm();
DefaultSecurityManager securityManager = new DefaultSecurityManager();
securityManager.setRealm(realm);
// SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
// Subject
UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
Subject subject = SecurityUtils.getSubject();
subject.login(token); // token错误,则抛出异常
System.out.println(subject.isAuthenticated()); // 是否已认证
subject.checkRoles("role_1" , "role_2"); // 角色检查失败,则抛出异常
subject.checkPermissions("user:delete", "user:update"); // 检查权限,检查失败则抛出异常
subject.logout(); // 登出
System.out.println(subject.isAuthenticated());
整合Spring
1、配置Maven依赖
<dependency>
<groupId>org.aspectj</groupId> <!-- 提供aop:config -->
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
2、web.xml中配置过滤器
DelegatingFilterProxy会根据filter-name提交给对应的Bean处理,因此这里的shiroFilter,是Spring配置文件里ShiroFilterFactoryBean的id;此外Spring MVC的springSessionRepositoryFilter不能省
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name> <!-- 让Spring容器来调用生命周期函数 -->
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3、Spring配置文件 applicationContext.xml
<bean name="realm" class="com.hogen.realm.MyRealm"></bean> <!-- 自定义Realm -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="realm"></property>
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"></property>
<property name="loginUrl" value="/login.html"></property> <!-- 未认证时访问需要认证的页面或接口,则跳转到login.html -->
<property name="unauthorizedUrl" value="/403.html"></property> <!-- 访问无权限的页面或接口,则跳转到403.html -->
<property name="filterChainDefinitions">
<!-- 如何校验权限,在这里配置 -->
<value>
/login.html = anon // 无需认证
/my/login.do = anon
/my/list.do = roles["role_1"] // 是某角色
/my/detail.do = perms["perms_1"] // 有某权限
/** = authc // 需要认证
</value>
</property>
</bean>
4、Spring MVC配置文件 dispatcher-servlet.xml
<aop:config proxy-target-class="true"></aop:config>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- 捕获Controller抛出的异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!-- Controller抛出这个异常时,则跳转到 jsp/403.jsp -->
<prop key="org.apache.shiro.authz.UnauthorizedException">403</prop>
<!-- Controller抛出这个异常时,则跳转到 jsp/login.jsp -->
<prop key="org.apache.shiro.authz.UnauthenticatedException">login</prop>
</props>
</property>
</bean>
5、Controller
@RequestMapping(value="/login", method=RequestMethod.POST)
@ResponseBody // 能把这个方法返回的字符串当做响应体返回
public ModelAndView login(){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
subject.login(token);
return "success";
}
// 通过了applicationContext.xml中配置的过滤器 shiroFilter后,才会到达这里校验权限
// 权限校验不通过时,则抛出异常,会被dispatcher-servlet.xml配置的SimpleMappingExceptionResolver 捕获
@RequiresRoles({"role_1", "role_2"}) // 是指定角色
@RequiresPermissions("admin") // 有指定权限
@RequestMapping(value="/list", method=RequestMethod.GET)
@ResponseBody
public String register(){
return "has permission";
}
网友评论