7.2 使用Security进行权限控制和防止 CSRF


权限控制

登录检查

  • 之前采用拦截器实现了登录检查,这是简单的权限管理方案,现在将其废弃。

  • 授权配置

    • 对当前系统内包含的所有的请求,分配访问权限(普通用户、版主、管理员)。
  • 认证方案

  • 绕过Security认证流程,采用系统原来的认证方案。

  • CSRF配置

​ - 防止 CSRF 攻击的基本原理,以及表单、AJAX相关的配置。

废弃拦截器

授权配置

增加常量

/**
 * 权限: 普通用户
 */
String AUTHORITY_USER = "user";

/**
 * 权限: 管理员
 */
String AUTHORITY_ADMIN = "admin";

/**
 * 权限: 版主
 */
String AUTHORITY_MODERATOR = "moderator";

配置路径和权限

配置不需要拦截的路径

哪些路径需要拦截,以及访问这些路径需要拥有的权限

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter implements CommunityConstant {

    //配置忽略拦截的路径(静态资源)
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**");
    }

    //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers(               //需要拦截的路径
                        "/user/setting",
                        "/user/upload",
                        "/discuss/add",
                        "/comment/add/**",
                        "/letter/**",
                        "/notice/**",
                        "/like",
                        "/follow",
                        "/unfollow"
                )
                .hasAnyAuthority(
                        AUTHORITY_USER,
                        AUTHORITY_ADMIN,
                        AUTHORITY_MODERATOR
                )
                .anyRequest().permitAll();
    }
}

处理请求失败

请求分为普通请求和异步请求,普通请求返回登录页面,而异步请求需要返回JSON字符串。

根据消息头判断请求方式;

失败的情况分为:没有登录的时候和登录了权限不够两种情况;

//处理失败
http.exceptionHandling()
        .authenticationEntryPoint(new AuthenticationEntryPoint() {
            //没有登录的情况
            @Override
            public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                String header = httpServletRequest.getHeader("x-requested-with");
                if("XMLHttpRequest".equals(header)){
                    //设置响应的类型和字符集
                    httpServletResponse.setContentType("application/plain;charset=utf-8");
                    PrintWriter writer = httpServletResponse.getWriter();
                    writer.write(CommunityUtil.getJSONString(403, "你还没有登录哦!"));
                }else {
                    //重定向到主页
                    httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/login");
                }
            }
        })
        .accessDeniedHandler(new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
                String xRequestedWith = request.getHeader("x-requested-with");
                if ("XMLHttpRequest".equals(xRequestedWith)) {
                    response.setContentType("application/plain;charset=utf-8");
                    PrintWriter writer = response.getWriter();
                    writer.write(CommunityUtil.getJSONString(403, "你没有访问此功能的权限!"));
                } else {
                    response.sendRedirect(request.getContextPath() + "/denied");
                }
            }
        });

覆盖Security的退出请求

// Security底层默认会拦截/logout请求,进行退出处理.
// 覆盖它默认的逻辑,才能执行我们自己的退出代码.
http.logout().logoutUrl("/securitylogout");

认证方案

编写通过userid获取当前用户的权限

通过user Type 获取相应的权限

//认证逻辑
public Collection<? extends GrantedAuthority> getAuthorities(int userId) {
    User user = this.queryUserById(userId);

    List<GrantedAuthority> list = new ArrayList<>();
    list.add(new GrantedAuthority() {

        @Override
        public String getAuthority() {
            switch (user.getType()) {
                case 1:
                    return AUTHORITY_ADMIN;
                case 2:
                    return AUTHORITY_MODERATOR;
                default:
                    return AUTHORITY_USER;
            }
        }
    });
    return list;
}

在拦截器中增加权限认证

//请求之初,controller之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //从cookie中获取ticket
    String ticket = CookieUtil.getValue(request, "ticket");
    if(ticket!=null)
    {
        //检查其状态是否有效
        LoginTicket loginTicket = userService.findLoginTicket(ticket);
        if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {
            // 根据凭证查询用户
            User user = userService.queryUserById(loginTicket.getUserId());
            // 在本次请求中持有用户
            hostHolder.setUser(user);

            // 构建用户认证的结果,并存入SecurityContext,以便于Security进行授权.
            Authentication authentication = new UsernamePasswordAuthenticationToken(
                    user, user.getPassword(), userService.getAuthorities(user.getId()));
            SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
        }
    }
    return true;
}

清理认证信息

//模板引擎调用之后 最后清理数据
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    hostHolder.clear();
    SecurityContextHolder.clearContext();
}

CSRF配置

  • 用户登录后,服务器会返回带有登录凭证的cookie,浏览器把cookie存储在本地‘
  • 当浏览器向服务器发送表单请求时,服务器返回提交表单页面;
  • 此时,保存在浏览器的cookie中的ticket被其他恶意网站窃取,并且恶意网站向服务器提交了POST表单的请求;
  • 如果是恶意提交(比如修改转账金额)之类的就会造成安全隐患;

Security的方法是服务器返回表单的时候,隐藏了一个TOCKEN信息,此信息为随机字符串,当用户提交表单的时候服务器会核对 ticket和TOCKEN;

在这里插入图片描述

异步请求需要自己处理

在异步处理的界面加上隐藏框

修改js页面

 发送AJAX请求之前,CSRF令牌设置到请求的消息头中.

  var token = $("meta[name='_csrf']").attr("content");
  var header = $("meta[name='_csrf_header']").attr("content");
  $(document).ajaxSend(function(e, xhr, options){
    xhr.setRequestHeader(header, token);
  });


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值