003-基于URL的权限管理[不使用shiro]
一、基于url权限管理流程【实现步骤】
二、环境搭建以及核心代码
https://github.com/bjlhx15/shiro.git
1、数据库
2、开发环境
3、系统工程框架
三、开发过程步骤
1、系统登录
主要功能:登录、记录用户session【包含用户信息,菜单信息,授权信息】
1.1、系统登陆相当于用户身份认证,用户成功,要在session中记录用户的身份信息.
1.2、操作流程:
用户进行登陆页面
输入用户名和密码进行登陆
进行用户名和密码校验
如果校验通过,在session记录用户身份信息
根据用户信息记录菜单menus【用于功能菜单展示】,记录权限资源permissions【用户判断功能项】,一起放到session中
1.3、激活用户基础类
public class ActiveUser implements java.io.Serializable { private String userid;//用户id(主键) private String usercode;// 用户账号 private String username;// 用户名称 private List<SysPermission> menus;// 菜单 private List<SysPermission> permissions;// 权限 public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getUsercode() { return usercode; } public void setUsercode(String usercode) { this.usercode = usercode; } public String getUserid() { return userid; } public void setUserid(String userid) { this.userid = userid; } public List<SysPermission> getMenus() { return menus; } public void setMenus(List<SysPermission> menus) { this.menus = menus; } public List<SysPermission> getPermissions() { return permissions; } public void setPermissions(List<SysPermission> permissions) { this.permissions = permissions; } }
1.4、mapperr接口: 根据用户账号查询用户(sys_user)信息(使用逆向工程生成的mapper)
使用逆向工程生成以下表的基础代码:
1.5、service(进行用户名和密码校验)
接口功能:根据用户的身份和密码 进行认证,如果认证通过,返回用户身份信息
认证过程:
根据用户身份(账号)查询数据库,如果查询不到用户不存在
对输入的密码 和数据库密码 进行比对,如果一致,认证通过
1.6、controller(记录session)
@RequestMapping("/login") public String login(HttpSession session, String randomcode,String usercode,String password)throws Exception{ //校验验证码,防止恶性攻击 //从session获取正确验证码 String validateCode = (String) session.getAttribute("validateCode"); //输入的验证和session中的验证进行对比 if(!randomcode.equals(validateCode)){ //抛出异常 throw new CustomException("验证码输入错误"); } //调用service校验用户账号和密码的正确性 ActiveUser activeUser = sysService.authenticat(usercode, password); //如果service校验通过,将用户身份记录到session session.setAttribute("activeUser", activeUser); //重定向到商品查询页面 return "redirect:/first.action"; }
2、用户认证拦截
主要功能:放行匿名url、判断用户session是否存在
2.1、配置匿名访问url【anonymousURL.properties】
#配置逆向访问的url login.action=用户登陆
2.2、编写认证拦截器
用于用户认证校验、用户权限校验
package com.lhx.ssm.controller.interceptor; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import com.lhx.ssm.po.ActiveUser; import com.lhx.ssm.util.ResourcesUtil; /** * * <p>Title: HandlerInterceptor1</p> * <p>Description: 用户身份认证拦截器</p> * @version 1.0 */ public class LoginInterceptor implements HandlerInterceptor { //在执行handler之前来执行的 //用于用户认证校验、用户权限校验 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //得到请求的url String url = request.getRequestURI(); //判断是否是公开 地址 //实际开发中需要公开 地址配置在配置文件中 //从配置中取逆名访问url List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL"); //遍历公开 地址,如果是公开 地址则放行 for(String open_url:open_urls){ if(url.indexOf(open_url)>=0){ //如果是公开 地址则放行 return true; } } //判断用户身份在session中是否存在 HttpSession session = request.getSession(); ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser"); //如果用户身份在session中存在放行 if(activeUser!=null){ return true; } //执行到这里拦截,跳转到登陆页面,用户进行身份认证 request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response); //如果返回false表示拦截不继续执行handler,如果返回true表示放行 return false; } //在执行handler返回modelAndView之前来执行 //如果需要向页面提供一些公用 的数据或配置一些视图信息,使用此方法实现 从modelAndView入手 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("HandlerInterceptor1...postHandle"); } //执行handler之后执行此方法 //作系统 统一异常处理,进行方法执行性能监控,在preHandle中设置一个时间点,在afterCompletion设置一个时间,两个时间点的差就是执行时长 //实现 系统 统一日志记录 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("HandlerInterceptor1...afterCompletion"); } }
2.3、配置拦截器
在springmvc.xml中配置拦截器:
<!--拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 用户认证拦截 -->
<mvc:mapping path="/**" />
<bean class="com.lhx.ssm.controller.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
3、授权
主要功能:放行匿名url、放行公共、获取用户session,授权资源判断
3.1、公共访问地址【commonURL.properties】
在此配置文件配置公用访问地址,公用访问地址只要通过用户认证,不需要对公用访问地址分配权限即可访问。
#配置公用的访问地址 first.action=系统首页 logout.action=退出
3.2、获取用户权限范围菜单
思路:在用户认证时,认证通过,根据用户id从数据库获取用户权限范围的菜单,将菜单的集合存储在session中。
po在三中的1.3已添加menus等属性
mapper xml
<!-- 根据用户id查询菜单 --> <select id="findMenuListByUserId" parameterType="string" resultType="com.lhx.ssm.po.SysPermission"> SELECT * FROM sys_permission WHERE TYPE = 'menu' AND id IN (SELECT sys_permission_id FROM sys_role_permission WHERE sys_role_id IN (SELECT sys_role_id FROM sys_user_role WHERE sys_user_id = #{id})) </select>
mapper接口
//根据用户id查询菜单 public List<SysPermission> findMenuListByUserId(String userid)throws Exception;
service接口:根据用户id查询用户权限的菜单
@Override public List<SysPermission> findMenuListByUserId(String userid) throws Exception { return sysPermissionMapperCustom.findMenuListByUserId(userid); }
3.3、获取用户权限范围的url
思路:在用户认证时,认证通过,根据用户id从数据库获取用户权限范围的url,将url的集合存储在session中。
po在三中的1.3已添加permissions等属性
mapper xml
<!-- 根据用户id查询url --> <select id="findPermissionListByUserId" parameterType="string" resultType="com.lhx.ssm.po.SysPermission"> SELECT * FROM sys_permission WHERE TYPE = 'permission' AND id IN (SELECT sys_permission_id FROM sys_role_permission WHERE sys_role_id IN (SELECT sys_role_id FROM sys_user_role WHERE sys_user_id = #{id})) </select>
mapper接口
//根据用户id查询权限url
public List<SysPermission> findPermissionListByUserId(String userid)throws Exception;
service接口:根据用户id查询用户权限的菜单
@Override
public List<SysPermission> findPermissionListByUserId(String userid)
throws Exception {
return sysPermissionMapperCustom.findPermissionListByUserId(userid);
}
3.4、用户认证通过后取出菜单和url放入ActiveUser,进而放入session
这里是三中的1.5 完善实现
package com.lhx.ssm.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import com.lhx.ssm.exception.CustomException; import com.lhx.ssm.mapper.SysPermissionMapperCustom; import com.lhx.ssm.mapper.SysUserMapper; import com.lhx.ssm.po.ActiveUser; import com.lhx.ssm.po.SysPermission; import com.lhx.ssm.po.SysUser; import com.lhx.ssm.po.SysUserExample; import com.lhx.ssm.service.SysService; import com.lhx.ssm.util.MD5; /** * * <p>Title: SysServiceImpl</p> * <p>Description:认证和授权的服务接口 </p> * @version 1.0 */ public class SysServiceImpl implements SysService { @Autowired private SysUserMapper sysUserMapper; @Autowired private SysPermissionMapperCustom sysPermissionMapperCustom; @Override public ActiveUser authenticat(String userCode, String password) throws Exception { /** 认证过程: 根据用户身份(账号)查询数据库,如果查询不到用户不存在 对输入的密码 和数据库密码 进行比对,如果一致,认证通过 */ //根据用户账号查询数据库 SysUser sysUser = this.findSysUserByUserCode(userCode); if(sysUser == null){ //抛出异常 throw new CustomException("用户账号不存在"); } //数据库密码 (md5密码 ) String password_db = sysUser.getPassword(); //对输入的密码 和数据库密码 进行比对,如果一致,认证通过 //对页面输入的密码 进行md5加密 String password_input_md5 = new MD5().getMD5ofStr(password); if(!password_input_md5.equalsIgnoreCase(password_db)){ //抛出异常 throw new CustomException("用户名或密码 错误"); } //得到用户id String userid = sysUser.getId(); //根据用户id查询菜单 List<SysPermission> menus =this.findMenuListByUserId(userid); //根据用户id查询权限url List<SysPermission> permissions = this.findPermissionListByUserId(userid); //认证通过,返回用户身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(sysUser.getId()); activeUser.setUsercode(userCode); activeUser.setUsername(sysUser.getUsername());//用户名称 //放入权限范围的菜单和url activeUser.setMenus(menus); activeUser.setPermissions(permissions); return activeUser; } //根据用户账号查询用户信息 public SysUser findSysUserByUserCode(String userCode)throws Exception{ SysUserExample sysUserExample = new SysUserExample(); SysUserExample.Criteria criteria = sysUserExample.createCriteria(); criteria.andUsercodeEqualTo(userCode); List<SysUser> list = sysUserMapper.selectByExample(sysUserExample); if(list!=null && list.size()==1){ return list.get(0); } return null; } @Override public List<SysPermission> findMenuListByUserId(String userid) throws Exception { return sysPermissionMapperCustom.findMenuListByUserId(userid); } @Override public List<SysPermission> findPermissionListByUserId(String userid) throws Exception { return sysPermissionMapperCustom.findPermissionListByUserId(userid); } }
3.5、菜单的动态显示需要前端配合
3.6、完整的授权拦截器代码【PermissionInterceptor】
package com.lhx.ssm.controller.interceptor; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import com.lhx.ssm.po.ActiveUser; import com.lhx.ssm.po.SysPermission; import com.lhx.ssm.util.ResourcesUtil; /** * * <p>Title: PermissionInterceptor</p> * <p>Description: 授权拦截器</p> * @version 1.0 */ public class PermissionInterceptor implements HandlerInterceptor { //在执行handler之前来执行的 //用于用户认证校验、用户权限校验 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //得到请求的url String url = request.getRequestURI(); //判断是否是公开 地址 //实际开发中需要公开 地址配置在配置文件中 //1.从配置中取逆名访问url List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL"); //遍历公开 地址,如果是公开 地址则放行 for(String open_url:open_urls){ if(url.indexOf(open_url)>=0){ //如果是公开 地址则放行 return true; } } //2.从配置文件中获取公共访问地址 List<String> common_urls = ResourcesUtil.gekeyList("commonURL"); //遍历公用 地址,如果是公用 地址则放行 for(String common_url:common_urls){ if(url.indexOf(common_url)>=0){ //如果是公开 地址则放行 return true; } } //3.获取session HttpSession session = request.getSession(); ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser"); //从session中取权限范围的url List<SysPermission> permissions = activeUser.getPermissions(); for(SysPermission sysPermission:permissions){ //权限的url String permission_url = sysPermission.getUrl(); if(url.indexOf(permission_url)>=0){ //如果是权限的url 地址则放行 return true; } } //4.执行到这里拦截,跳转到无权访问的提示页面 request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(request, response); //如果返回false表示拦截不继续执行handler,如果返回true表示放行 return false; } //在执行handler返回modelAndView之前来执行 //如果需要向页面提供一些公用 的数据或配置一些视图信息,使用此方法实现 从modelAndView入手 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("HandlerInterceptor1...postHandle"); } //执行handler之后执行此方法 //作系统 统一异常处理,进行方法执行性能监控,在preHandle中设置一个时间点,在afterCompletion设置一个时间,两个时间点的差就是执行时长 //实现 系统 统一日志记录 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("HandlerInterceptor1...afterCompletion"); } }
3.7、配置授权拦截器
注意:将授权拦截器配置在用户认证拦截的下边。
<!--拦截器 --> <mvc:interceptors> <mvc:interceptor> <!-- 用户认证拦截 --> <mvc:mapping path="/**" /> <bean class="com.lhx.ssm.controller.interceptor.LoginInterceptor"></bean> </mvc:interceptor> <mvc:interceptor> <!-- 授权拦截 --> <mvc:mapping path="/**" /> <bean class="com.lhx.ssm.controller.interceptor.PermissionInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
四、小结
使用基于url拦截的权限管理方式,实现起来比较简单,不依赖框架,使用web提供filter就可以实现。
问题:
需要将所有的url全部配置起来,有些繁琐,不易维护,url(资源)和权限表示方式不规范。
如果漏配置,就会无权访问。
URL也是一种资源