Spring整合shiro授权

授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限。

:::tip 注意

注意:授权是基于认证基础之上的

:::

授权的三种方式

  • 编程方式:如使用if判断当前subject是否有权限,这种方法判断时候实际上已经进入了方法内部。
  • 注解方式:在方法上添加权限注解,在进入方法前进行验证。
  • jsp标签方式:通过shiro提供的jsp标签根据权限确定是否对用户显示按钮等操作,如果直接根据url方式请求则可以直接访问不需要授权。

授权步骤

1.在需要授权的方法上添加@RequirePermission注解

在使用注解时需要注意三点:①注解本身,必须要定义个注解 ②被注解的类,的注解要放在方法上还是类上 ③怎样才能让注解生效

比如我在查询列表上添加@RequiresPermissions("user:list")注解,就表示访问这个方法需要用户拥有user 的llist权限才能访问,否则就会抛出UnauthorizedException这个异常。

2.在spring-shiro文件中配置代理类解析注解

    <!-- 开启aop,对类代理 -->
    <aop:config proxy-target-class="true"></aop:config>
    <!-- 开启shiro注解支持 -->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>

:::warning 注意:

我在Controller添加了此方法后,此时在我的UserRealm中授权信息是空,也就是说当前用户没有任何权限,但是当我访问了这个方法之后居然还可以正常访问。。。。通过百度大法找到了问题的答案:


我们知道Shiro的注解授权是利有Spring的AOP实现的。在程序启动时会自动扫描作了注解的Class,当发现注解时,就自动注入授权代码实现。也就是说,要注入授权控制代码,第一处必须要让框架要可以扫描找被注解的Class 。

spring.xml

<context:component-scan base-package="cn.edu.neusoft" >
    <context:exclude-filter type="annotation"        expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

spring-mvc.xml

<context:component-scan base-package="cn.edu.neusoft" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

而我们的Srping项目在ApplicationContext.xml中一般是不扫描Controller的,所以也就无法让写在Controller中的注解授权生效了。因此正确的作法是将这配置放到springmvc的配置文件中.这样Controller就可以通过注解授权了。

不过问题来了,通过上面的配置Controller是可以通过注解授权了,但是Services中依然不能通过注解授权。虽然说,如果我们在Controller控制了授权,那么对于内部调用的Service层就可以不再作授权,

但也有例外的情况,比如Service除了给内部Controller层调用,还要供远程SOAP调用,那么就需要对Service进行授权控制了。同时要控制Controller和Service,那么采用相同的方式,我们可以在ApplicationContext.xml中配置类同的配置,以达到相同的效果。
:::

3.配置Shiro的异常处理

因为SpringMVC有一个默认的异常处理机制,所以需要定义Shiro的特粗异常处理程序才能生效,这里就是如果有无权限这个异常重定向到nopermisson这个页面

<!--shiro权限异常处理-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="org.apache.shiro.authz.UnauthorizedException">redirect:/admin/system/nopermission</prop>
            </props>
        </property>
    </bean>

::: tip 没有用ajax的忽略这里:

到这里就完成第一步了,不过又遇到一个问题,我是使用ajax请求,而在请求之前没到Controller时候请求就已经被拦截了,响应回了一个nopermission这个页面,而用ajax有不能自动跳转到这个页面,很难受。。。

正在解决中。。(找了一天终于解决了):

参考文章:https://blog.csdn.net/catoop/article/details/69210140

原理就是通过一个 BaseController 来统一处理,然后被其他 Controller 继承即可,对于JSON和页面跳转,我们只需要做一个Ajax判断处理即可。

BaseController代码:

/**
 * @author Chen
 * @create 2019-05-02 19:26
*/
public abstract class BaseController {


 /**
     * 权限异常
  */
 @ExceptionHandler({ UnauthorizedException.class, AuthorizationException.class })
 public String authorizationException(HttpServletRequest request, HttpServletResponse response) {
     if (WebUtilsPro.isAjaxRequest(request)) {
         // 输出JSON
         Map<String,Objectmap = new HashMap<>();
         map.put("type", "error");
         map.put("msg", "无权限");
         writeJson(map, response);
         return null;
     } else {
         return "redirect:/system/403";
     }
 }

 /**
     * 输出JSON
  *
     * @param response
     * @author SHANHY
     * @create 2017年4月4日
  */
 private void writeJson(Map<String,Objectmap, HttpServletResponse response) {
     PrintWriter out = null;
     try {
         response.setCharacterEncoding("UTF-8");
         response.setContentType("application/json; charset=utf-8");
         out = response.getWriter();
         //这里用了一个阿里的fastjson,直接添加一下依赖即可
         out.write(String.valueOf(new JSONObject(map)));
     } catch (IOException e) {
         e.printStackTrace();
     } finally {
         if (out != null) {
             out.close();
         }
     }
 }
}

WebUtilsPro 代码:

package cn.edu.neusoft.common;

import javax.servlet.http.HttpServletRequest;

/**
 * @author Chen
 * @create 2019-05-02 19:27
*/
public class WebUtilsPro {

 /**
     * 是否是Ajax请求
  *
     * @param request
     * @return
     * @author SHANHY
     * @create 2017年4月4日
  */
 public static boolean isAjaxRequest(HttpServletRequest request) {
     String requestedWith = request.getHeader("x-requested-with");
     if (requestedWith != null && requestedWith.equalsIgnoreCase("XMLHttpRequest")) {
         return true;
     } else {
         return false;
     }
 }
}

然后继承这个BaseController 就OK 了。
:::

4.加载权限表达式

::: tip 说明:
我们用注解在很多个方法上都标注了权限,用户必须有这个权限才能进行访问,在Realm需要将用户具有的权限告诉Shiro,而这么多方法肯定要保存到数据库中,我们也不能一个一个去加到数据库中。

我们通过获取注解中的属性来添加到数据库:(数据库中存了权限表达式以及权限名称)

由于Shiro没有给我们提供自定义权限名称的注解,于是我们自定义一个注解用于获取权限名称添加到数据库中。
:::
PermissionController:

/**
 * @author Chen
 * @create 2019-05-01 20:28
 */
@Controller
@RequestMapping("admin/permission")
public class PermissionController {

    @Autowired
    private PermissionService permissionService;

    //请求映射处理映射器
    //springmvc在启动时候将所有贴有请求映射标签:RequestMapper方法收集起来封装到该对象中
    @Autowired
    private RequestMappingHandlerMapping rmhm;


    @ResponseBody
    @RequestMapping("reload")
    public Map<String,Object> reload(){
        Map<String,Object> map = new HashMap<String, Object>();
        //将系统中所有权限表达式加载进入数据库
        //0:从数据库中查询出所有权限表达式,然后对比,如果已经存在了,跳过,不存在添加
        List<String> resourcesList = permissionService.getAllResources();
        //1:获取controller中所有带有@RequestMapper标签的方法
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = rmhm.getHandlerMethods();
        Collection<HandlerMethod> methods = handlerMethods.values();
        for (HandlerMethod method : methods) {
            //2:遍历所有方法,判断当前方法是否贴有@RequiresPermissions权限控制标签
            RequiresPermissions anno = method.getMethodAnnotation(RequiresPermissions.class);
            if(anno != null){
                //3:如果有,解析得到权限表达式,封装成Permission对象保存到Permission表中
                //权限表达式
                String resource = anno.value()[0];

                //去除重复的
                if(resourcesList.contains(resource)){
                    continue;
                }
                Permission p = new Permission();
                p.setResource(resource);
                //设置权限名称
                p.setName(method.getMethodAnnotation(PermissionName.class).value());
                //保存
                permissionService.addPermission(p);
            }
        }

        map.put("type","success");
        map.put("msg","加载成功!");
        return map;
    }



}

注解类PermissionName

package cn.edu.neusoft.realm;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Chen
 * @create 2019-05-01 20:20
 * 自定义权限名称注解
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionName {
    String value();
}

我们只要像这样加上注解请求指定路径即可将权限加载到数据库中。

@RequiresPermissions("user:delete")
@PermissionName("用户删除")
public Map<String,Object> deleteUser(@RequestParam(required = true) Long id){
    ······

5.整合数据库授权

这里就可以把对应角色的权限告诉Shiro让他来判断已经登录的角色是否有权限访问了

修改UserRealm类的doGetAuthorizationInfo方法:

/**
 * @author Chen
 * @create 2019-04-30 15:27
 */
public class UserRealm extends AuthorizingRealm {
    @Setter
    private UserService userService;
    @Setter
    private RoleService roleService;
    @Setter
    private PermissionService permissionService;


    @Override
    public String getName() {
        return "UserRealm";
    }

    /**
     * 授权
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        User user = (User) principalCollection.getPrimaryPrincipal();
        List<String> permissions = new ArrayList<String>();
        List<String> roles = new ArrayList<String>();

        if ("admin".equals(user.getUsername())){
            //让超级管理员拥有所有权限
            permissions.add("*:*");
            //查询所有角色
            roles = roleService.getRoleByUserId(user.getId());
        }else {
            //根据用户ID查询该用户具有的角色
            roles = roleService.getRoleByUserId(user.getId());
            //根据用户ID查询该用户具有的权限
            permissions = permissionService.getAllPermissionsByUserId(user.getId());
        }

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissions);
        info.addRoles(roles);
        return info;
    }

    /**
     * 认证
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //从token中获取登录的用户名, 查询数据库返回用户信息
        String username = (String) token.getPrincipal();
        User user = userService.getUserByUsername(username);

        if (user == null) {
            return null;
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
        return info;
    }

}

不要忘了在Spring-shiro.xml注入对应的Service

这里根据用户ID查询角色信息以及权限信息用到了多表查询,顺便复习了一下,需要的点击下方链接:

http://ishangit.cn/?id=54

在把对应的sql语句放上来把:

<select id="getAllPermissionsByUserId" parameterType="Long" resultType="String">
        select resource from user u
        join user_role ur on u.id = ur.user_id
        join role_permission rp on ur.role_id = rp.role_id
        join permission p on rp.permission_id = p.id
        where u.id = #{id}
    </select>
<mapper namespace="cn.edu.neusoft.dao.RoleDao">
    <select id="getRoleByUserId" resultType="String" parameterType="Long">
        select sn from role r 
        join user_role u on r.id = u.role_id where user_id = #{id}
    </select>

到这里我们的授权功能就完成啦。

猜你喜欢

转载自www.cnblogs.com/chen88/p/11538363.html
今日推荐