权限控制
认证授权
概念
认证:系统提供的用于识别用户身份的功能,通常提供用户名和密码进行登录操作其实就是在进行认证,认证的目的是让系统知道是谁
授权:用户认证成功后,需要为用户授权,其实就是指定当前用户可以操作哪些功能
分析
权限控制的基本实现思路:
先通过用户名和密码对用户进行认证,认证就是判断当前用户是否是系统用户
然后再根据用户ID或用户名获取权限信息,即授权信息
授权信息是用户角色及权限标识的集合,把用户的角色,权限标识集合与用户关联,最终放入集合中.
每个用户的授权信息,只需要在登录的时候加载一次即可,所以我们把授权的代码放在登录成功之后执行
认证授权实现
UserService
修改login方法, 添加授权(加载用户的权限)
//判断查询的用户名和密码是否有值
if(user!=null){
//先创建一个list集合,用来存储用户加载的角色
List<String>list=newArraryList<>();
//调用Dao层加载用户拥有的角色
RoleDaoroledao=SqlSessiopn.getMapping(RoleDao.class);
//传入用户的id属性,返回Role集合
List<Role>roleList=roledao.findByUid(user.getId);
//判断该用户是否有权限 (Dao层返回的对象是否有值)
if(roleList!=null&&rolelist.size()>0){
//加载每个用户对应的角色的权限资源
PermissionDaopermission=sqlSession.getMapper(PermissionDao.class);
for (Rolerole:roleList){
//循环将role的keyWord存入list集合
list.add(role.getKeyword());
//调用Dao层,传入roleid作为参数
List<Permission>permissionList=permission.findByRoledId(role.getId);
if (permissionList!=null){
for (Permissionpermission:permissionList){
list.add(permission.getKeyword());
}
}
}
}
//将list集合作为参数修改到user中的authorityList集合属性中
user.setAuthorityList(list)
}
RoleDao
List<Role>findByUid(Integerid);//调用映射文件对应的id标签
RoleDao.xml
<selectid="findByUid"parameterType="int"resultType="Role">
select * from t_role where id in(select role_id from tr_user_role where user_id=#{uid})
</select>
PermissionDao
List<Permission>findByRoleId(Integerid);//调用映射文件对应的id标签
PermissionDao.xml
<mappernamespace="com.itheima.mm.dao.PermissionDao">
<selectid="findByRoleId"parameterType="int"resultType="Permission">
select * from t_permission where id in(select permission_id from tr_role_permission where role_id=#{roleId})
</select>
自定义权限框架流程
权限控制实现步骤
配置权限资源
如果一个资源,需要指定权限/角色才能访问,那么:要把资源和权限/角色的关系进行配置
可以采用两张配置结合使用:
XML配置 : 用于对一些资源文件(如:html文件)的权限进行配置
注解配置 : 用于对web层方法的权限进行配置
加载配置
当服务器启动时 ,要获取资源所需要的权限配置
拦截请求 ,判断
当前登录的用户拥有的权限,是否包含了访问资源所需要的权限
自定义框架-基于XML
创建配置文件
文件名称:mm-security.xml
文件位置:放在resources目录下
文件说明:
beans:根标签
security标签:一个标签,配置一个资源所需要的角色
pattern:资源路径,这里配置的是html页面的路径
has_role:资源所需要的角色。多个角色之间,用逗号隔开
表示:访问pattern资源,用户必须有has_role中定义的角色之一;否则无权限
<?xmlversion="1.0" encoding="UTF-8" ?>
<beans>
<securitypattern="/pages/index.html"has_role="ROLE_ADMIN,ROLE_QUESTION_RECORDER"/>
<securitypattern="/pages/questionBasicList.html"has_role="ROLE_ADMIN,ROLE_QUESTION_RECORDER"/>
<securitypattern="/pages/questionClassicList.html"has_role="ROLE_ADMIN"/>
<securitypattern="/pages/userList.html"has_role="ROLE_ADMIN"/>
<!--注解扫描包。配置web层的包名-->
<scanpackage="<!--自定义注解所在的包名-->"/>
</beans>
创建过滤器
publicclassSecurityFilterimplementsFilter{
//创建一个map集合用来存存储配置文件中的pattern属性(键) 和 has_role属性(值)
privateMap<String,String>map=newHashMap<>();
@Override
publicvoidinit(FilterConfigfilterConfig)throwsServletException{
//需要加载配置文件mm-security.xml 得到每个页面资源可以访问的角色
InputStreaminputStream=null;
try{
//通过过滤器的FilterConfig对象,可以得到web.xml里,配置的初始化参数
StringconfigLocation=filterConfig.getInitParameter("configLocation");
inputStream=this.getClass().getClassLoader().getResourceAsStream(configLocation);
//读取解析xml文件,得到里边的配置 使用的方法:dom4j
SAXReaderreader=newSAXReader();
Documentdocument=reader.read(inputStream);
/* 方法一
//获取xml文件里所有的security标签
//先找到根标签 ,再根据根标签找到里边的子标签
Element rootElement=document.getRootElement();
rootElement.element("security");*/
//方法二 用xpath表达式 //security,表示从xml文件里全文搜索security标签
List<Element>security=document.selectNodes("//security");
for(ElementsecurityElement:security){
//获取security标签的pattern属性值:资源路径
Stringpattern=securityElement.attributeValue("pattern");
//获取security标签的has_role属性值:哪些角色可以访问
Stringhas_role=securityElement.attributeValue("has_role");
//将读取的数据存储到Map集合,以资源路径作为键,以has_role的属性值作为值
map.put(pattern,has_role);
}
}catch(Exceptione) {
e.printStackTrace();
}finally{
try{
if(inputStream!=null) {
//关闭输入流
inputStream.close();
}
}catch(IOExceptione) {
e.printStackTrace();
}
}
}
/**
* 权限过滤: 判断用户拥有的权限,能否访问目标资源
* @param request
* @param response
* @param chain
*/
@Override
publicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{
HttpServletRequestreq=(HttpServletRequest)request;
HttpServletResponseres=(HttpServletResponse)response;
//获取客户端请求的资源是,去掉项目资源和无关后缀
Stringur=req.getRequestURI();
StringcontextPath=req.getContextPath();
Stringuri=ur.substring(contextPath.length());//去掉项目路径
if(uri.endsWith(".do")){//判断如果结尾是.do,则替换成""
uri=uri.replace(".do","");
}
//从map里获取访问资源所需要的角色(权限)
StringhasRole=map.get(uri);
//如果客户端请求的页面在xml文件中没有配置资源路径,则直接放行
if(hasRole==null){
//不需要进行权限控制
System.out.println(uri+"不需要权限控制.直接放行");
chain.doFilter(req,res);
return;
}
//获取当前用户拥有的角色(权限)
Useruser=(User)req.getSession().getAttribute("user");
//如果没有登录
if(user==null){
System.out.println(uri+"访问的用户没有登录");
res.sendRedirect(contextPath+"/login.html");
return;
}
//已登录 得到当前用户拥有的权限
List<String>authorityList=user.getAuthorityList();
//如果当前用户
if(authorityList==null||authorityList.size()==0){
res.getWriter().print(user.getUsername()+"用户无权限");
System.out.println(user.getUsername()+"用户没有权限");
return;
}
booleancanAccess=false;//定义一个布尔变量 旗帜变量
//判断用户的权限,是否足够 用户拥有的角色(权限)里,是否包含目标资源所需要的任何一个角色(权限)
//用逗号进行分割 [ROLE_ADMIN],[ROLE_QUESTION_RECORDER]
String[]split=hasRole.split(",");
for(Strings:split){
//如果数据表中包含这个权限
if(authorityList.contains(s)){
canAccess=true;
break;
}
}
if(canAccess){
System.out.println(uri+"权限足够,放行");
chain.doFilter(req,res);
}else{
System.out.println(uri+"当前用户"+user.getUsername()+"权限不足,请切换用户");
res.getWriter().print("<<路径:"+uri+">>\n当前用户"+user.getUsername()+",权限不足,请切换用户");
}
}
过滤器配置(web.xml)
为了提高程序的可用性,解决硬编码问题,我们采用web.xml配置过滤器
<!--配置权限过滤器-->
<filter>
<filter-name><!--过滤器的名称--></filter-name>
<filter-class><!--过滤器的所在的全限定类名--></filter-class>
<!--初始化参数-->
<init-param>
<param-name><!--定义一个名称--></param-name>
<param-value><!--过滤器配置文件名.xml--></param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name><!--过滤器名称--></filter-name>
<url-pattern>/*</url-pattern><!--过滤的范围-->
</filter-mapping>
自定义框架-基于注解
创建注解
@Target(ElemenType.METHOD) //元注解: 注解定义的位置
@Retention(RetentionPolicy.RUNTIME)//元注解: 注解存活的期限
public@interfacePreAuthorize{
Stringvalue();
}
在控制器方法上增加注解配置权限
@PreAuthorize("QUESTION_REVIEW_UPDATE")
@RequestMapping("/review/reviewQuestion")
publicvoidreviewQuestion(HttpServletRequestrequest,HttpServletResponseresponse)throwsIOException{
//......内容省略了.......重点看方法上增加的注解
}
3.在过滤器中的int方法里增加代码
privateMap<String,String>map=newHashMap<>();
@Override
publicvoidinit(FilterConfigfilterConfig)throwsServletException{
//需要加载配置文件mm-security.xml 得到每个页面资源可以访问的角色
InputStreaminputStream=null;
try{
//通过过滤器的FilterConfig对象,可以得到web.xml里,配置的初始化参数
StringconfigLocation=filterConfig.getInitParameter("configLocation");
inputStream=this.getClass().getClassLoader().getResourceAsStream(configLocation);
//读取解析xml文件,得到里边的配置 dom4j
SAXReaderreader=newSAXReader();
Documentdocument=reader.read(inputStream);
/* 方法一
//获取xml文件里所有的security标签
//先找到根标签 ,再根据根标签找到里边的子标签
Element rootElement=document.getRootElement();
rootElement.element("security");*/
//方法二 用xpath表达式 //security,表示从xml文件里全文搜索security标签
List<Element>security=document.selectNodes("//security");
for(ElementsecurityElement:security){
//获取security标签的pattern属性值:资源路径
Stringpattern=securityElement.attributeValue("pattern");
//获取security标签的has_role属性值:哪些角色可以访问
Stringhas_role=securityElement.attributeValue("has_role");
//将读取的数据存储到Map集合,以资源路径作为键,以has_role的属性值作为值
map.put(pattern,has_role);
}
//扫描Controller类中的方法 得到:资源 权限 是否可以访问
//先扫描xml文件中的package标签,得到里面的值
ElementscanEelement=(Element)document.selectSingleNode("//scan");
StringPackageName=scanEelement.attributeValue("package");
//扫描指定包下所有的类
List<Class<?>>classsFromPackage= ClassScannerUtils.getClasssFromPackage(PackageName);
if(classsFromPackage!=null&&classsFromPackage.size()>0) {
//循环这个包下所有的类
for(Class<?>aClass:classsFromPackage) {
//先判断这个类是不是控制器
booleanannotationPresent=aClass.isAnnotationPresent(Controller.class);
if(annotationPresent){
//获取该类中的全部方法
Method[]methods=aClass.getMethods();
for(Methodmethod:methods){
//扫描类中所有方法,判断是否有@RequestMapping注解
booleanisRequestMapping= method.isAnnotationPresent(PreAuthorize.class);
if(isRequestMapping){
//得到资源的访问路径
RequestMappingannotation=method.getAnnotation(RequestMapping.class);
StringmappingPath=annotation.value();
//得到资源的权限
PreAuthorizepreAuthorize=method.getAnnotation(PreAuthorize.class);
Stringvalue=preAuthorize.value();
//将这些数据保存到map中 以访问路径做键(@RequestMapping的值) 以@PreAuthorize的值为值
map.put(mappingPath,value);
}
}
}
}
}
}catch(Exceptione) {
e.printStackTrace();
}finally{
try{
if(inputStream!=null) {
inputStream.close();
}
}catch(IOExceptione) {
e.printStackTrace();
}
}
}