仿牛客社区——7.3权限控制

实现功能

采用SpringSecurity进行用户的权限控制管理

登录检查

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

授权配置

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

认证方案

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

注意:

关于认证:该项目未走security的认证方式,而是走自己实现的认证方式。

 在security中进行认证时,是将认证信息封装进UsernamePasswordAuthenticationToken(如果认证信息是账号密码)中  ,而security底层的filter会将token存到securityContext中。

后面对用户进行权限判断时,要从securityContext中取出认证过的授权信息 。

这里我们需要绕过security底层认证逻辑,但是还要取到用户的权限信息,所以我们提供一个获取用户权限的方法,将用户权限在存入securityContex中 。

CSRF配置

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

csrf攻击是指某网站盗取用户的cookie中的凭证,冒充用户身份去访问服务器并向表单中提交数据。

发生在提交表单时,springsecurity解决方案:springsecurity在服务器向浏览器返回表单时,会返回一个隐藏的token(凭证),每次token都是随机的。

Code

首先向该项目中增加一些常量,用来赋予用户权限(写在了常量接口CommunityConstant中)

/**
 * 权限:普通用户
 */
String AUTHORITY_USER="user";
/**
 * 权限:管理员
 */
String AUTHORITY_ADMIN="admin";
/**
 * 权限:版主
 */
String AUTHORITY_MODERATOR="moderator";

SecurityConfig

import com.light.community.util.CommunityConstant;
import com.light.community.util.CommunityUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author light
 * @Description  springSecurity配置类
 * @create 2023-05-31 15:24
 */
@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(
						"/comment/add/**",
						"/discuss/add",
						"/follow",
						"/unfollow",
						"/like",
						"/letter/**",
						"/user/setting",
						"/user/upload",
						"/notice/**"
				)
				.hasAnyAuthority(
						AUTHORITY_ADMIN,
						AUTHORITY_MODERATOR,
						AUTHORITY_USER
				)
				.anyRequest().permitAll(); //除了这些请求以外任何请求都是允许访问的
				//.and().csrf().disable();   //不启用防止CSRF攻击


		//权限不够时的处理
		http.exceptionHandling()  //不同请求,返回结果不同(eg:同步、异步,因此用处理器更合适一点
				.authenticationEntryPoint(new AuthenticationEntryPoint() {
					@Override
					public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
						//没有登录时处理
						//考虑请求方式:如果普通请求,直接重定向到页面;如果异步请求,要返回json字符串
						//通过请求头判断请求方式是同步或异步
						String xRequestedWith = request.getHeader("x-requested-with");
						if("XMLHttpRequest".equals(xRequestedWith)){
							//异步请求
							response.setContentType("application/plain;charset=utf-8");  //响应字符串:返回响应数据类型:普通字符串(plain
							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 {
						//登陆了但是权限不足时的处理
						//考虑请求方式:如果普通请求,直接重定向到页面;如果异步请求,要返回json字符串
						//通过请求头判断请求方式是同步或异步
						String xRequestedWith = request.getHeader("x-requested-with");
						if("XMLHttpRequest".equals(xRequestedWith)){
							//异步请求
							response.setContentType("application/plain;charset=utf-8");  //响应字符串:返回响应数据类型:普通字符串(plain
							PrintWriter writer = response.getWriter();
							writer.write(CommunityUtil.getJsonString(403,"你还没有访问此功能权限!"));
						}else{

							//同步请求
							response.sendRedirect(request.getContextPath()+"/denied");

						}

					}
				});


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


		/**
		 * 关于认证:该项目为走security的认证方式,而是自己实现的认证方式,
		 * 在security中进项认证,实现将认证信息封装进UsernamePasswordAuthenticationToken(如果认证信息是账号密码)中
		 * security底层的filter会将token存到securityContext中,后面进项权限判断时,要从securityContext中取出认证过的授权信息
		 * 因此我们要绕过security底层认证逻辑,但是还要取到用户的权限信息,所以我们提供一个获取用户权限的方法,将用户权限在存入securityContex中
		 */
	}
}

UserService

 //获取用户权限
    public Collection<? extends GrantedAuthority> getAuthority(int userId){
        User user = this.findUserById(userId);
        List<GrantedAuthority> list=new ArrayList<>(); //将用户权限信息存入list中
        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;
    }

 LoginTicketIntercepter(获取用户权限,对用户进行授权

 @Override  //在controller之前拦截
    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);
            //查验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(
                        user,user.getPassword(),userService.getAuthority(user.getId())
                );

                SecurityContextHolder.setContext(new SecurityContextImpl(authentication));

            }
        }
        return true;
    }

防止CSRF攻击

当提交表单数据时,security会默认带上一个 token,但是当发送异步请求时,security不会提供隐藏的token,需要手动请求头中添加,这里仅演示index页面中的异步请求,即发帖功能的异步请求

index.html 

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html"> <!--声明使用的模板引擎-->
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
	<!-- <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous"> -->
	<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" crossorigin="anonymous"/>
	<link rel="stylesheet" th:href="@{/css/global.css}" /><!--相对路径,用thymeleaf语法处理-->

	<!-- 在访问此页是,在此处自动生成CSRF令牌 (异步请求不提交表单时-->
	<meta name="_csrf" th:content="${_csrf.token}">
	<meta name="_csrf_header" th:content="${_csrf.headerName}">    <!--通过请求消息头传值-->

	<title>牛客网-首页</title>
</head>

index.js

$(function(){
	$("#publishBtn").click(publish);
});

function publish() {
	$("#publishModal").modal("hide");

	//发送Ajax请求之前,提前将CSRF令牌设置到请求的消息头中
	var token=$("meta[name='_csrf']").attr("content");
	var header=$("meta[name='_csrf_header']").attr("content");
	//在发送请求之前对页面进行设置   xhr:发送异步请求的核心对象
	$(document).ajaxSend(function(e,xhr,options){
	    xhr.setRequestHeader(header,token);  //设置请求头
	});

	//获取标题和内容
	var title=$("#recipient-name").val();//id选择器
	var content=$("#message-text").val();

	//发送异步请求(POST)
	$.post(
	    CONTEXT_PATH+"/discuss/add",
	    {"title":title,"content":content},
	    function(data){
	        data=$.parseJSON(data);
	        //在提示框中返回消息
	        $("#hintBody").text(data.msg);
	        //显示提示框
	        $("#hintModal").modal("show");
	        //两秒后自动隐藏
            setTimeout(function(){
                $("#hintModal").modal("hide");
                //判断是否发送成功
                if(data.code==0){
                    window.location.reload(); //重新加载页面
                }
            }, 2000);

	    }
	);


}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值