权限管理属于系统安全的范畴,是实现对用户访问系统的控制,可以按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
- 用户身份认证
身份认证,就是判断一个用户是否为合法用户。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
认证流程:
在实际项目中有些资源用户可以直接访问,有些资源必须对用户什么进行认证(检测用户的合法性),认证通过则可以访问,认证失败则不允许访问.
关键术语:
Subject:主体(被要求认证和授权管理的主体)
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
Principal:身份信息
是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,
一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
- 用户身份授权
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。
授权流程:
关键术语:
授权一般指允许who(Subject)对what(Resource)执行how(Permission)操作。
Resource:系统中的资源,一般会对应一个Url,这个url的表现一般为菜单。
Permission:对资源的访问标识(例如user:add,user:update,user:delete,user:view)
主题,资源,权限在权限管理系统中通常会划分为不同的模块,例如其描述图如下:
权限设计模型:
权限设计模型如图所示:
实际应用中经常还会将权限和资源合为一张表:权限(权限名称、资源名称、资源访问地址)
权限访问控制:
基于角色的访问控制(RBAC -Role-Based Access Control):扩展性相对较差
基于资源的访问控制(RBAC-Resource-Based Access Control):扩展性比较好
在使用RBAC时,实际应用中又分粗粒度权限控制和细粒度的权限控制,粗粒度一般指对资源类型的访问控制(例如用户可以导出所有订单明细),细粒度一般对具体某个资源实例的访问控制(例如只能导出自己工资信息),对于粗粒度的权限控制可以使用过滤器或spring拦截器都可以,对于细粒度的权限控制一般都会在业务层进行相关判定。
Shiro是apache旗下一个开源安全框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架,使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。
课后了解:Spring security 安全框架
Shiro 基本架构:
通过Shiro框架进行权限管理时,要涉及到的一些核心对象,主要包括:
认证管理对象,授权管理对象,会话管理对象,缓存管理对象,加密管理对象
以及Realm管理对象(领域对象:负责处理认证和授权领域的数据访问题)
Shiro 认证流程:
具体流程分析如下:
1、首先调用Subject.login(token)进行登录,其会自动委托给Security Manager,调用之前必须通过SecurityUtils. setSecurityManager()设置;
2、SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;
3、Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现;
4、Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;
5、Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。
Shiro 应用实践:
shiro应用目前主要讲解shiro是集成到spring如何实现权限控制
假设本项目spring配置已经基本实现
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>
构建Realm类:
通过Realm实现基本认证及权限控制
public class ShiroUserRealm extends AuthorizingRealm{
@Resource
private SysUserDao sysUserDao;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
System.out.println("==doGetAuthorizationInfo==");
SysUser user = (SysUser) SecurityUtils.getSubject()
.getSession().getAttribute("currentUser");
int userId = user.getId();
List<String> permsList = new ArrayList<>();
permsList = sysUserDao.findUserPermissions(userId);
//用户权限列表
Set<String> permsSet = new HashSet<String>();
for(String perm : permsList){
if(perm!=null && !("".equals(perm))){
permsSet.add(perm);
}
}
System.out.println("permsSet="+permsSet);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
System.out.println("==doGetAuthenticationInfo==");
//1. 把 AuthenticationToken 转换为 UsernamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2. 从 UsernamePasswordToken 中来获取 username
String username = upToken.getUsername();
//判断用户名是否存在,若存在,返回user对象
SysUser user =sysUserDao.findObjectByName(username);
//盐值.
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getSalt());
//自动完成密码比对 - 密码的比对:
//通过 AuthenticatingRealm 的 credentialsMatcher 属性来进行的密码的比对!
SimpleAuthenticationInfo info =
new SimpleAuthenticationInfo(
username, user.getPassword(),credentialsSalt,getName());
SecurityUtils.getSubject().getSession()
.setAttribute("currentUser",user);
return info;
}
}
@Service
public class SysShiroServiceImpl implements SysShiroService {
@Override
public void login(String username, String password) {
Subject subject = SecurityUtils.getSubject();
if(subject.isAuthenticated())return;
// 把用户名和密码封装为 UsernamePasswordToken 对象
UsernamePasswordToken token =
new UsernamePasswordToken(username, password);
try{//登录认证 - 调用userRealm
subject.login(token);
}catch (IncorrectCredentialsException ice) {
throw new ServiceException("密码错误!");
} catch(AuthenticationException ae){
ae.printStackTrace();
throw new ServiceException("认证失败");
}
}
}
登录认证Controller实现:
@Controller
public class SysLoginController {
@Autowired
private SysShiroService loginService;
@RequestMapping("/loginUI")
public String login(){
return "login";
}
/**登录操作*/
@RequestMapping("/login")
@ResponseBody
public JsonResult login(String username,String password){
System.out.println(username+"/"+password);
loginService.login(username, password);
return new JsonResult();
}
}
Shiro 注解应用:
Shiro中通过使用@RequiresPermissions注解实现对某些方法的标识权限标识,例如
修改Web.xml:
<!-- spring整合shiro安全框架 -->
<filter>
<filter-name>DelegatingFilterProxy</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 初始化参数 -->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DelegatingFilterProxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
添加spring-shiro配置文件:
<!-- shiro工厂bean配置 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- shiro的核心安全接口 -->
<property name="securityManager" ref="securityManager"/>
<!-- 要求登录时的连接 -->
<property name="loginUrl" value="/toLogin.do"></property>
<!-- 登录成功后要跳转的连接(此处已经在登录中处理了) -->
<!-- <property name="successUrl" value="/index.jsp"></property> -->
<!-- 访问未对其授权的资源时,要跳转的连接
<property name="unauthorizedUrl" value="/default.html"></property>-->
<!-- shiro连接约束配置 -->
<property name="filterChainDefinitions">
<value>
<!-- 对静态资源设置允许匿名访问 -->
/images/** = anon
/js/** = anon
/css/** = anon
/static/** = anon
/bootstrap/** = anon
/jquery/** = anon
<!-- 可匿名访问路径,例如:验证码、登录连接、退出连接等 -->
/confirmUser.do = anon
<!-- 退出 -->
/logout.do = logout <!-- 会调用Subject的logout方法,此方法会将session清空 -->
<!-- 剩余其他路径,必须认证通过才可以访问 -->
/** = authc
</value>
</property>
</bean>
<!-- 配置shiro安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"></property>
</bean>
<!-- 自定义Realm -->
<bean id="userRealm" class="com.jt.system.service.ShiroUserRealm">
<!-- 配置凭证算法匹配器 -->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"/>
<!-- <property name="hashIterations" value="1024"/> -->
</bean>
</property>
</bean>
<!--Shiro生命周期处理器-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!--启用shiro注解权限检查-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>