7.2 权限控制

80 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第32天,点击查看活动详情

7.2 权限控制

image-20220728064021361

引入 Spring Security 依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

登录检查

废弃之前采用的登录检查拦截器(不需要删除拦截器类,只要让其配置不生效即可)

image-20220728065834891

授权配置

现在常量接口里增加几个常量(表示权限,下面好使用)

CommunityConstant

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

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

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

image-20220728070430873

然后在 HomeController 里处理一下没有权限跳转到 404 界面

image-20220728073824507

接下来对 Security授权相关的配置

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
                )
                .anyRequest().permitAll();            // 除了上面的路径其他路径不管登录没登录都可以访问

        // 权限不够时的处理
        http.exceptionHandling()
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
                    // 没有登录怎么处理
                    @Override
                    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                        String xRequestedWith = request.getHeader("x-requested-with");
                        if ("XMLHttpRequest".equals(xRequestedWith)) {      // 异步请求,返回json
                            response.setContentType("application/plain;charset=utf-8"); //声明要返回的数据的类型,普通字符串
                            PrintWriter writer = response.getWriter();       // 获得字符流
                            writer.write(CommunityUtil.getJSONString(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 {
                            // 可以走到这说明已经登录了但是没有权限,我们跳转到 /denied 路径
                            response.sendRedirect(request.getContextPath() + "/denied");
                        }
                    }
                });

        // Security底层默认会拦截 /logout 请求,进行退出处理.
        // 覆盖它默认的逻辑,才能执行我们自己的退出代码.
        http.logout().logoutUrl("/securitylogout");// "/securitylogout"其实不存在,只是一个善意的欺骗,好让程序执行到我们的方法里
    }

}

认证方案

认证没处理它就会走 LoginController 我们自己的认证 ,但是有一个问题,上一次写的demo的认证逻辑里会把认证的信息封装到 tooken 里,这个tooken会被security的一个Filter获取到然后这个Filter会把这个tooken存到SecurityContext里,后面判断有没有权限的时候都是从SecurityContext得到这个tooken判断权限,所以我们绕过了它认证的逻辑,但是那个结论我们还得存到 SecurityContext 里面,因为我们已经绕过了它认证的逻辑,所以就没必要像上次demo那样User实现一个接口UserService实现一个接口,没必要这么复杂,但是我们也需要做一些必要的处理,在 UserService 里加一段逻辑

我们最终要把用户的权限存到SecurityContext里,首先这个用户查到以后具有什么权限,我们还得做一个适配,就是提供根据用户获得用户权限的方法,当需要的时候调一下就可以了。另外关键是我们什么时候去获得用户的权限并且把用户权限的tooken存到SecurityContext里,之前实现是在 LoginTicketInterceptor 里,

public Collection<? extends GrantedAuthority> getAuthorities(int userId) {
    User user = this.findUserById(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;
}

image-20220728083126635

image-20220728083954741

image-20220728084144092

image-20220728084355767

CSRF配置

所谓CSRF攻击就是浏览器向服务器发送了一个提交表单请求,此时浏览器访问了另一个不安全的网站,这个网站获取到了浏览器的cookie,而浏览器的cookie存着登录凭证,这个时候这个不安全的网站会伪装自己为浏览器向服务器提交表单,如果这个表单是转账相关的业务的话,那就比较危险了。那Security是怎么解决这种情况的呢,服务器在向浏览器发送表单页面同时会发送一个tooken,浏览器提交表单时得把这个tooken也提交了,那个不安全的网站是没办法获取这个tooken,但是这种方式只能解决同步请求,如果是异步请求我们得在页面上强制生成tooken,我们的每一个异步请求都得这么处理。

如果不想使用CSRF认证我们在SecurityConfig禁用掉CSRF就可以了它就不走这个逻辑了就ok了,但如果想防止CSRF工具,挨个处理就好了。

image-20220728085117730


在项目里我们是没有使用CSRF配置的,如果想使用可以像下面这样配置,但是在项目里我们是没有配置的

image-20220728091231460

image-20220728092028841


在项目中我们是这样处理的

image-20220728092907238

image-20220728093009171

image-20220728093032339

最后以前开发时有一点小问题

image-20220728094218377

image-20220728094242643

image-20220728094307728

image-20220728094415275

image-20220728094434566

image-20220728094453770

在启动项目的时候 kafka 一定要打开