7.3权限控制

将上节课的security技术应用到牛客社区,管理项目中现有权限

开发的时候,从以下四个方向入手:

在这里插入图片描述
CSRF是一种常用的攻击手段。

1.要废弃掉拦截器,则在WebMvcConfig中,注释掉:

在这里插入图片描述
在这里插入图片描述

2.授权配置

在CommunityConstant常量接口中,新加

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

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

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

接下来进行配置:在SecurityConfig

@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
                )
                .antMatchers(
                        "/discuss/top",
                        "/discuss/wonderful"
                )
                .hasAnyAuthority(
                        AUTHORITY_MODERATOR
                )
                .antMatchers(
                        "/discuss/delete",
                        "/data/**"
                )
                .hasAnyAuthority(
                        AUTHORITY_ADMIN
                )
                .anyRequest().permitAll()//除了以上请求外,都允许
                .and().csrf().disable();

        // security底层对权限不够时的处理。异步请求,返回json,普通请求返回html
        http.exceptionHandling()
                .authenticationEntryPoint(new AuthenticationEntryPoint() {//实例化接口
                    // 没有登录
                    @Override
                    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                       //判断当前请求是同步or异步,看请求消息头的某一个值
                        String xRequestedWith = request.getHeader("x-requested-with");
                        if ("XMLHttpRequest".equals(xRequestedWith)) {//如果该字符串的值是XMLHttpRequest,则为异步请求。异步请求期待返回XML
                            response.setContentType("application/plain;charset=utf-8");//声明数据返回的类型。plain表示普通的字符串。charset字符集为utf-8 支持中文
                            PrintWriter writer = response.getWriter();
                            writer.write(CommunityUtil.getJSONString(403, "你还没有登录哦!"));//没有权限是403状态码
                        } else {
                            response.sendRedirect(request.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");//在homecontroller中定义了"/denied"
                        }
                    }
                });

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

}

在userservices中添加:查询用户权限的方法

  public Collection<? extends GrantedAuthority> getAuthorities(int userId) {//查询用户权限的方法,希望获得userId用户的权限
        User user = this.findUserById(userId);

        List<GrantedAuthority> list = new ArrayList<>();//什么时候获得用户权限,并且把用户权限的结论tocken存到context里,之前也做过显示用户登录信息的功能,登录成功以后会生成一个ticket,存到用户里,用户每次访问服务器,服务器会验证此ticket,看此凭证对不对,有没有过期
        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;
    }

在LoginTicketInterceptor中,

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    // 从cookie中获取凭证
    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.findUserById(loginTicket.getUserId());
            // 在本次请求中持有用户
            hostHolder.setUser(user);
            
			//新建:
            // 构建用户认证的结果,并存入SecurityContext,以便于Security进行授权.
            Authentication authentication = new UsernamePasswordAuthenticationToken(//authentication是认证结果(凭证),UsernamePasswordAuthenticationToken里面要存user、用户密码、权限
                    user, user.getPassword(), userService.getAuthorities(user.getId()));
            SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
        }
    }

    return true;
}

```c
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    hostHolder.clear();
    SecurityContextHolder.clearContext();
}

找到退出的功能,也进行清理:

@RequestMapping(path = "/logout", method = RequestMethod.GET)
public String logout(@CookieValue("ticket") String ticket) {
    userService.logout(ticket);
    SecurityContextHolder.clearContext();
    return "redirect:/login";
}

最后配置CSRF,最终统一测试

浏览器登录服务器后,服务器给其发了一个ticket,且已经存到cookie中,
此时浏览器又向服务器发送了请求,
在这里插入图片描述
按理说用户应该填表单,但是并没有

用户访问了不正经网址,里面有病毒窃取了cookie,因此该网址可以模仿用户身份访问服务器,
在这里插入图片描述
这就是CSRF攻击

而当引入security后,服务器再返回的时候,会默认启用tocken(凭证),所以表单里会有tocken
而不正经网址,虽然窃取了cookie,但没有tocken。

这就是security 应对CSRF攻击的方式。

这就是 表单:
在这里插入图片描述
网页的源码为
在这里插入图片描述
这就是security为应对CSRF攻击生成的凭证。
在这里插入图片描述
在index.html中,
在这里插入图片描述
在index.js中,
在这里插入图片描述

重新编译:

在首页的源码中
在这里插入图片描述
发布帖子后有了add请求,
在这里插入图片描述
在request中,
在这里插入图片描述
正是因为有了此限制,才能避免csrf攻击。

如果不这么处理,服务器得不到tocken
最后都注释掉????????
在这里插入图片描述
在这里插入图片描述
下面在SecurityConfig第63行中,不启用csrf

   .and().csrf().disable();
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值