SpringSecurity授权相关

授权准备工作

为了模拟授权操作,临时编写两个业务功能:
处理器代码:

// ProductController 
@Controller
@RequestMapping("/product")
public class ProductController {
    @RequestMapping("/findAll")
    public String findAll() {
        return "product-list";
    }
}

// OrderController 
@Controller
@RequestMapping("/order")
public class OrderController {
    @RequestMapping("/findAll")
    public String findAll() {
        return "order-list";
    }
}

aside.jsp页面:

<ul class="treeview-menu">
    <li id="system-setting"><a href="${pageContext.request.contextPath}/product/findAll"> <i class="fa fa-circle-o"></i>
        产品管理</a></li>
    <li id="system-setting"><a href="${pageContext.request.contextPath}/order/findAll"> <i class="fa fa-circle-o"></i>
        订单管理</a></li>
</ul>

动态展示菜单

在aside.jsp对每个菜单通过SpringSecurity标签库指定访问所需角色
权限控制使用security的动态标签<security:authorize>实现,通过指定角色的方式来决定某些菜单是否展示

<!-- 只有ROLE_PRODUCT跟ROLE_ADMIN才能访问 -->
<security:authorize access="hasAnyRole('ROLE_PRODUCT', 'ROLE_ADMIN')">
    <li id="product-setting">
        <a href="${pageContext.request.contextPath}/product/findAll">
            <i class="fa fa-circle-o"></i> 产品管理
        </a>
    </li>
</security:authorize>

<!-- 只有ROLE_ORDER跟ROLE_ADMIN才能访问 -->
<security:authorize access="hasAnyRole('ROLE_ORDER', 'ROLE_ADMIN')">
    <li id="order-setting">
        <a href="${pageContext.request.contextPath}/order/findAll">
            <i class="fa fa-circle-o"></i> 订单管理
        </a>
    </li>
</security:authorize>

通过这个标签,不同角色的用户在登录后会有不同的菜单展示,但是这并非真正的实现了授权,因为通过地址栏手动输入url仍然可以访问到系统不希望该角色访问的页面

授权操作

首先说明项目IOC容器的结构,如下图,首先任何web工程都有一个最大的web容器ServletContext,然后在此基础上产生spring IOC父容器跟子容器,http请求只能够访问子容器而不能访问父容器,想要访问父容器必须通过子容器调用,这也就是springMVC安全的地方。

说明:SpringSecurity可以通过注解的方式来控制类或者方法的访问权限。注解需要对应的注解支持,若注解放在
controller类中,对应注解支持应该放在mvc配置文件中,因为controller类是由mvc配置文件扫描并创建的,同
理,注解放在service类中,对应注解支持应该放在spring配置文件中。由于现在是模拟业务操作,并没有
service业务代码,所以就把注解放在controller类中了。

开启授权的注解支持

<!--
 开启权限的注解支持
 secured-annotations="enabled":SpringSecurity内部的权限控制注解开关
 pre-post-annotations="enabled":spring指定的权限控制的注解开关
 jsr250-annotations="enabled":开启java250注解支持
 -->
<security:global-method-security secured-annotations="enabled"
                                 pre-post-annotations="enabled"
                                 jsr250-annotations="enabled"/>

由于是在Controller层添加权限控制注解,那么这段配置代码也应该放到Controller对应的spring-mvc配置文件中,而不是spring-security配置文件中,否则会失效

在对应类或者方法上添加注解

对应三种注解开关,注解的方式也有三种,如下

@Controller
@RequestMapping("/product")
public class ProductController {

//    @Secured({"ROLE_PRODUCT", "ROLE_ADMIN"})   // springSecurity内部制定的注解
//    @RolesAllowed({"ROLE_PRODUCT", "ROLE_ADMIN"})   // jsr250注解
    @PreAuthorize("hasAnyRole('ROLE_PRODUCT', 'ROLE_ADMIN')")   // spring的el表达式注解
    @RequestMapping("/findAll")
    public String findAll(){
        return "product-list";
    }
}

权限不足异常处理

经过上面的幸苦,权限问题已经基本解决了,但是还有一个问题,那就是每次权限不足都会出现403页面,这实在不美观,现在应该做的就是消灭它!

方式一:在spring-security.xml配置文件中处理

<security:http auto-config="true" use-expressions="true">
    <!-- 处理403异常 -->
    <security:access-denied-handler error-page="/403.jsp"/>
</security:http>

方式二:在web.xml中处理

<!-- 处理403异常 -->
<error-page>
    <error-code>403</error-code>
    <location>/403.jsp</location>
</error-page>

方式三:编写异常处理器

在controller包下面新建一个异常处理包,并创建异常处理类。按照如下的写法,就可以控制住异常跳转的页面

import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class HandlerControllerException implements HandlerExceptionResolver {

    /**
     *
     * @param httpServletRequest request
     * @param httpServletResponse response
     * @param o 出现异常的对象
     * @param e 出现的异常信息
     * @return ModelAndView
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        ModelAndView mv = new ModelAndView();
        // 将异常信息放入request域中
        mv.addObject("errMsg", e.getMessage());
        // 指定不同异常跳转的页面
        if (e instanceof AccessDeniedException) {
            mv.setViewName("forward:/403.jsp");   // forward表示不经过视图解析器
        } else {
            mv.setViewName("forward:/500.jsp");   // forward表示不经过视图解析器
        }
        return mv;
    }
}

上面的写法虽然可以,但是稍显麻烦,其实还可以使用下面的写法

import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class HandlerControllerException2 {

    @ExceptionHandler(AccessDeniedException.class)
    public String except403Advice() {
        return "forward:/403.jsp";
    }

    @ExceptionHandler(RuntimeException.class)
    public String exceptOtherAdvice() {
        return "forward:/500.jsp";
    }
}

很明显,第二种方式代码更加清晰简便些。

posted @ 2020-04-28 18:27  Jin同学  阅读(135)  评论(0)    收藏  举报