Springboot + Spring Security 前后端分离权限控制

前言

最近因为领导要求,要把现在项目改成前端段分离的形式,因此,本文不再具体讲述Spring Security中如何实现用户动态权限认证以及带有图片验证码的自定义认证部分。所以适合对Security有一定了解的伙伴看一下。
看了一些帖子实现方式都一样,只是在于权限的认证不同而已,所以本文基于已经实现security登录认证及权限控制,主要针对登陆成功登录失败退出登录未登录无权访问会话到期这几部分进行拦截处理并返回json字符串。

简单的说,只需要在security配置文件中,对应位置的跳转URL配置改为拦截处理而已,因此只需要有六个拦截实现类以及修改六处配置,就都OK了。自己原有的其他逻辑不受影响。

至于其他具体实现细节,根据读者自己的实际需求添加吧。
代码Git
在此先声明一下,验证码验证部分存在问题,可以看我的另一篇,已经把验证码部分修改为过滤器处理。

一、前期准备

1.1 统一的返回实体类

只是为了统一返回json格式,可以不用。
本文使用的json包为com.alibaba.fastjson,需要注意的是使用JSONJSONObject转换的时候效果是一样的,不同的是JSONObject把返回实体转为json字符串的时候实现**Serializable **序列化方法,而JSON没有实现。

import java.io.Serializable;

public class ResponseBody implements Serializable {
 
    /**
	 * 
	 */
	private static final long serialVersionUID = 1886106011131539131L;
	
	private String status;
    private String msg;
    private Object result;

    public String getStatus() {
        return status;
    }
 
    public void setStatus(String status) {
        this.status = status;
    }
 
    public String getMsg() {
        return msg;
    }
 
    public void setMsg(String msg) {
        this.msg = msg;
    }
 
    public Object getResult() {
        return result;
    }
 
    public void setResult(Object result) {
        this.result = result;
    }
 
}

二、Spring Security核心配置:WebSecurityConfig

此处列举了修改前后端分离前后的配置,并进行对比查看。各项功能跳转的路径都进行修改成拦截返回、关闭跨域、完善会话等。

2.1修改前配置

关于配置的含义都已经进行备注,就不再多说了。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import net.cnki.security.MyAuthenctiationSuccessHandler;
import net.cnki.security.MyAuthenticationProvider;


@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	private MyAuthenctiationSuccessHandler myAuthenctiationSuccessHandler;
//	@Autowired
//	private MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.headers().frameOptions().disable(); //用于加载页面iframe部分
		http.authorizeRequests()
				.antMatchers("/getVerify","/css/**", "/js/**", "/image/**", "/fonts/**", "/images/**", "/lib/**","/ws/**").permitAll() // 允许所有用户访问
				//.antMatchers("/**").hasRole("admin") // 仅允许admin角色访问全部
				//.antMatchers("/**").access("hasAnyRole('FILE','USER')") // 仅允许具备其中某一角色的用户访问
				//.antMatchers("/**").access("hasRole('admin') or hasRole('child')") // 仅允许同时具备两个角色的用户访问
				.anyRequest().authenticated()
				.and()
			.formLogin() // 定义当需要用户登录时候,转到的登录页面
				.loginPage("/login") //自定义的登录页,不写的话调用security内部的.loginProcessingUrl("/beacon/user/login")//默认登录的方法
				.failureUrl("/login?error=true")
				.defaultSuccessUrl("/index")//成功登录后跳转页面
				.successHandler(myAuthenctiationSuccessHandler)
				//.failureHandler(myAuthenctiationFailureHandler)
				.permitAll()
				.and()
			.sessionManagement()
				.invalidSessionUrl("/login")//session失效后跳转路径
				//.sessionFixation().newSession()//用户认证之后,会新创建一个session,但是不会将旧的session中的属性,迁移到新的session中(旧的也可以用,不建议)。默认.migrateSession()新建属性从原session中拷贝过来
				.and()
			.requestCache().disable()//使退出前的操作请求缓存为空失效,但是并没有更改获取缓存路径并跳转的实现,避免登录后跳转到上一次操作嗯对全路径下而非主页
			.logout()
				.logoutSuccessUrl("/login") //成功退出后跳转到的页面
				.permitAll()//退出
				.and()
			.csrf().ignoringAntMatchers("/druid/*");//druid监控web界面开放
		
//		http.requestCache().requestCache(new NullRequestCache());//与disable相似,disable()同样实现了new NullRequestCache(),此处记录学习
//		http.sessionManagement().maximumSessions(1).expiredUrl("/login");//会话管理:用户仅允许一个登陆
	}

	//加入中间验证层,可实现自定义验证用户等信息
	@Bean
    public AuthenticationProvider authenticationProvider() {
        AuthenticationProvider provider = new MyAuthenticationProvider();
        return provider;
    }
	
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }

}

2.2修改后配置

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import net.cnki.security.hander.MyAuthenctiationDeniedHandler;
import net.cnki.security.hander.MyAuthenctiationEntryPointHandler;
import net.cnki.security.hander.MyAuthenctiationFailureHandler;
import net.cnki.security.hander.MyAuthenctiationInvalidSessionStrategy;
import net.cnki.security.hander.MyAuthenctiationLogoutSuccessHandler;
import net.cnki.security.hander.MyAuthenctiationSessionInformationExpiredStrategy;
import net.cnki.security.hander.MyAuthenctiationSuccessHandler;
import net.cnki.security.hander.MyAuthenticationProvider;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	MyAuthenctiationEntryPointHandler myAuthenctiationEntryPointHandler;//未登录
	@Autowired
	MyAuthenctiationSuccessHandler myAuthenctiationSuccessHandler;//登陆成功
	@Autowired
	MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;//登录失败
	@Autowired
	MyAuthenctiationDeniedHandler myAuthenctiationDeniedHandler;//无权访问
	@Autowired
	MyAuthenctiationLogoutSuccessHandler myAuthenctiationLogoutSuccessHandler;//退出成功
	@Autowired
	MyAuthenctiationInvalidSessionStrategy mMyAuthenctiationInvalidSessionStrategy;//session到期
	@Autowired
	MyAuthenctiationSessionInformationExpiredStrategy myAuthenctiationSessionStrategy;//session到期,被登陆

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
			.antMatchers("/getVerify","/session/invalid").permitAll()
			.anyRequest().authenticated()
			.and()
			.exceptionHandling()
				.authenticationEntryPoint(myAuthenctiationEntryPointHandler)//未登录402
				.accessDeniedHandler(myAuthenctiationDeniedHandler)//无权访问403
			.and()
			.formLogin() //定义登录拦截
				.successHandler(myAuthenctiationSuccessHandler)//登陆成功200
				.failureHandler(myAuthenctiationFailureHandler)//登陆失败401
				.permitAll()
				.and()
			.sessionManagement()//session到期提示
//				.invalidSessionUrl("/session/invalid")//效果略有差异,此处即便有过期session,多次访问后都会进入未登录拦截,下边则只要存在过期cookies就会一直在过期拦截
				.invalidSessionStrategy(mMyAuthenctiationInvalidSessionStrategy)//session到期101
				.and()
			.requestCache().disable()
			.logout()
				.logoutSuccessHandler(myAuthenctiationLogoutSuccessHandler)//退出登陆200
//				.deleteCookies("JSESSIONID")
				.permitAll()//退出
				.and()
			.csrf().disable();//csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());//csrf放开配置方式可以为cookie
//		http.sessionManagement().maximumSessions(1).expiredSessionStrategy(myAuthenctiationSessionStrategy);//101只允许一个登陆,新的顶替就得
	}

	//加入中间验证层,可实现自定义验证用户等信息
	@Bean
    public AuthenticationProvider authenticationProvider() {
        AuthenticationProvider provider = new MyAuthenticationProvider();
        return provider;
    }
	
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }

}

三、配置拦截

security配置中去除相应跳转页面之后,改为对应的拦截类,并实现不同的内置方法。包含登陆成功登录失败退出登录未登录无权访问会话到期六部分,其中会话管理在单独进行讲述。

3.1 登录成功

import java.io.IOException;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;

import net.cnki.common.ResponseBody;

/**
 * 登录成功
 * @author ZhiPengyu
 *
 */
@Component("myAuthenctiationSuccessHandler")
public class MyAuthenctiationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		logger.info("登录成功!");
		
		ResponseBody responseBody = new ResponseBody();
		responseBody.setStatus("200");
    	responseBody.setMsg("Login Success!");
    	response.getWriter().write(JSON.toJSONString(responseBody));
	}  
}

3.2 登录失败

import java.io.IOException;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;

import net.cnki.common.ResponseBody;

/**
 * 登录失败
 * @author ZhiPengyu
 *
 */
@Component("myAuthenctiationFailureHandler")
public class MyAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException, ServletException {
		logger.info("登录失败!");
		
		ResponseBody responseBody = new ResponseBody();
		responseBody.setStatus("401");
        responseBody.setMsg("Login Failure!");
		response.getWriter().write(JSON.toJSONString(responseBody));
	}
}

3.3 退出登录

import java.io.IOException;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;

import net.cnki.common.ResponseBody;

/**
 * 退出登录
 * @author ZhiPengyu
 *
 */
@Component
public class MyAuthenctiationLogoutSuccessHandler implements LogoutSuccessHandler{
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Override
	public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
			throws IOException, ServletException {
		logger.info("退出登录!");
		
		ResponseBody responseBody = new ResponseBody();
		responseBody.setStatus("200");
        responseBody.setMsg("Logout Success!");
        response.getWriter().write(JSON.toJSONString(responseBody));
	}
}


3.4 未登录

import java.io.IOException;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;

import net.cnki.common.ResponseBody;

/**
 * 未登录
 * @author ZhiPengyu
 *
 */
@Component
public class MyAuthenctiationEntryPointHandler implements AuthenticationEntryPoint{
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException, ServletException {
		logger.info("未登录!");
		
		ResponseBody responseBody = new ResponseBody();
        responseBody.setStatus("402");
        responseBody.setMsg("Need Login!");
        response.getWriter().write(JSON.toJSONString(responseBody));
	}
}

3.5 无权访问

import java.io.IOException;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;

import net.cnki.common.ResponseBody;

/**
 * 无权访问
 * @author ZhiPengyu
 *
 */
@Component
public class MyAuthenctiationDeniedHandler implements AccessDeniedHandler {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response,
			AccessDeniedException accessDeniedException) throws IOException, ServletException {
		logger.info("无权访问!");
		
		ResponseBody responseBody = new ResponseBody();
	    responseBody.setStatus("403");
	    responseBody.setMsg("Need Authorities!");
	    response.getWriter().write(JSON.toJSONString(responseBody));
	}
}

3.6 session到期

import java.io.IOException;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;

import net.cnki.common.ResponseBody;


/**
 * session到期
 * @author ZhiPengyu
 *
 */
@Component
public class MyAuthenctiationInvalidSessionStrategy implements InvalidSessionStrategy{
	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Override
	public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
			throws IOException, ServletException {
		logger.info("session到期!");
		
		ResponseBody responseBody = new ResponseBody();
	    responseBody.setStatus("101");
	    responseBody.setMsg("Session Expires!");
	    response.getWriter().write(JSON.toJSONString(responseBody));
		
	}
	

}

四、登录校验

4.1用户角色权限校验

import java.util.ArrayList;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import net.cnki.usermanage.bean.SysUser;
import net.cnki.usermanage.service.SysUserService;

@Component
public class MyUserDetailService implements UserDetailsService {
	Logger logger = LoggerFactory.getLogger(MyUserDetailService.class);
	
    @Autowired
    private SysUserService sysUserService;
//    @Autowired
//    private HttpServletRequest httpServletRequest;
    
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    	//用户验证前先验证是否有验证码
//    	String requestCode = httpServletRequest.getParameter("vercode");
//        if(StringUtils.isEmpty(requestCode)) {
//        	logger.info("验证码不能为空!");
//        	throw new UsernameNotFoundException("验证码不能为空!");
//        }
        if(StringUtils.isEmpty(username)) {
        	logger.info("用户名不能为空!");
        	throw new UsernameNotFoundException("用户名不能为空!");
        }
        //通过用户名获取用户信息
        SysUser user =  null;
		try {
			user =sysUserService.selectByUserName(username);
		} catch (Exception e) {
			throw new UsernameNotFoundException("系统异常!");
		}
        //SysUser user = sysUserService.selectByUserName(username);
        if (user == null){
        	logger.info("登录用户"+username+"不存在!");
            throw new UsernameNotFoundException("登录用户不存在!");
        }else if(user.getStatus() == -1){
        	logger.info("登录用户"+username+"已禁用!");
        	throw new UsernameNotFoundException("登录用户已禁用!");
        }
        String role = "";
        if(user.getRole() ==1) {
        	role = "admin";
        }else if(user.getRole() ==2) {
        	role = "child";
        }
        //获取用户的角色
        ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        //角色必须以`ROLE_`开头
        grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role));

        return new org.springframework.security.core.userdetails.User(user.getUsername(),
                user.getPassword(),//若入库密码已进行加密,此处则不需要解密
                grantedAuthorities);
    }
}

4.2验证码校验

import java.util.Collection;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * 自定义校验-密码、图片验证码
 * @author ZhiPengyu
 *
 */
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
	Logger logger = LoggerFactory.getLogger(MyAuthenticationProvider.class);
	
    @Autowired
    private MyUserDetailService userService;
    @Autowired
    HttpServletRequest httpServletRequest;
    
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    
    /**
     * 自定义验证方式
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();

        UserDetails user = userService.loadUserByUsername(username);
        
        //加密过程在这里体现
        logger.info("结果CustomUserDetailsService后,已经查询出来的数据库存储密码:" + user.getPassword());
        if (!passwordEncoder().matches(password, user.getPassword())) {
        	logger.info("登录用户密码错误!");
            throw new DisabledException("登录用户密码错误!");
        }
 
//        String requestCode = httpServletRequest.getParameter("vercode");
//        HttpSession session = httpServletRequest.getSession();
//		String saveCode = (String) session.getAttribute("RANDOMVALIDATECODEKEY");//captcha
//		//获取到session验证码后随时清除
//		if(!StringUtils.isEmpty(saveCode)) {
//			session.removeAttribute("RANDOMVALIDATECODEKEY");//captcha
//		}
//		logger.info("requestCode:"+requestCode+",saveCode:"+saveCode);
//		if(StringUtils.isEmpty(saveCode) || StringUtils.isEmpty(requestCode) || !requestCode.equals(saveCode)) { 
//			logger.info("图片验证码错误!");
//			throw new DisabledException("图形验证码错误!"); 
//		}
//		logger.info("登录成功");
		
        Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
        return new UsernamePasswordAuthenticationToken(user, password, authorities);
    }
 
    @Override
    public boolean supports(Class<?> arg0) {
        return true;
    }
}

五、security跨域处理以及json请求

security的跨域真的是一个大坑,涉及的地方还是很多的,很容易就挂了。json需要处理登录等默认表单提交情况,登录有响应头必须配置完全,否则前端无法成功。问题较多,遇到的时候可以留言探讨

5.1、WebMvcConfigurer配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import net.cnki.common.handler.AuthenticationInterceptor;

@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
	//-----------------------------------跨域----------------------------------
	private CorsConfiguration buildConfig() {
	    CorsConfiguration corsConfiguration = new CorsConfiguration();
	    corsConfiguration.addAllowedOrigin("http://192.168.52.26:3000");
	    corsConfiguration.addAllowedHeader("*");
	    corsConfiguration.addAllowedMethod("*");
	    corsConfiguration.addExposedHeader("Authorization");
	    return corsConfiguration;
	}
	
	@Bean
	public CorsFilter corsFilter() {
	    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
	    source.registerCorsConfiguration("/**", buildConfig());
	    return new CorsFilter(source);
	}
	 
	@Override
	public void addCorsMappings(CorsRegistry registry) {
	    registry.addMapping("/**")//设置允许跨域的路径
	            .allowedOrigins("http://192.168.52.26:3000")//设置允许跨域请求的域名,"http://localhost:3000"
	            .allowCredentials(true)//是否允许证书 不再默认开启
	            .allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH")//设置允许的方法
	            .maxAge(3600);//跨域允许时间
	}
	//-------------------------------------token拦截器--------------------------------------
	@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/login","/logout","/getVerify","/lib/**","/ws/**","/swagger-ui.html","/doc.html", "/v2/**", "/webjars/**", "/swagger-resources/**");
    }
	
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}

5.2、WebSecurityConfigurerAdapter配置

需要添加cors().and().csrf().disable().anonymous().disable()跨域及无用户状态,以及两个addFilterBefore过滤

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsUtils;

import net.cnki.common.msgreturn.ResultGenerator;
import net.cnki.common.service.UserTokenService;
import net.cnki.security.filter.CorsFilter;
import net.cnki.security.filter.LoginAuthenticationFilter;
import net.cnki.security.hander.MyAuthenctiationDeniedHandler;
import net.cnki.security.hander.MyAuthenctiationEntryPointHandler;
import net.cnki.security.hander.MyAuthenctiationFailureHandler;
import net.cnki.security.hander.MyAuthenctiationInvalidSessionStrategy;
import net.cnki.security.hander.MyAuthenctiationLogoutSuccessHandler;
import net.cnki.security.hander.MyAuthenctiationSessionInformationExpiredStrategy;
import net.cnki.security.hander.MyAuthenctiationSuccessHandler;
import net.cnki.security.verify.MyAuthenticationProvider;
import net.cnki.usermanage.service.SysUserService;
import net.cnki.usermanage.service.UserRoleFineGrainedService;

/**
 * 
 * @Description: Security配置类,前后端分离
 * @author ZhiPengyu
 * @date: 2020年5月7日 上午10:20:28
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	MyAuthenctiationSuccessHandler myAuthenctiationSuccessHandler;
	@Autowired
	MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;
	@Autowired
	MyAuthenctiationDeniedHandler myAuthenctiationDeniedHandler;//无权访问
	@Autowired
	MyAuthenctiationEntryPointHandler myAuthenctiationEntryPointHandler;//未登录
	@Autowired
	MyAuthenctiationInvalidSessionStrategy mMyAuthenctiationInvalidSessionStrategy;//session到期
	@Autowired
	MyAuthenctiationLogoutSuccessHandler myAuthenctiationLogoutSuccessHandler;//退出成功
	@Autowired
	MyAuthenctiationSessionInformationExpiredStrategy myAuthenctiationSessionStrategy;//session到期,被登陆
	@Autowired
	ResultGenerator resultGenerator;
	@Autowired
	AuthenticationUser aAuthenticationUser;
	@Autowired
	SysUserService sysUserService;
	@Autowired
	UserTokenService userTokenService;
	@Autowired
	UserRoleFineGrainedService userRoleFineGrainedService;
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.headers().frameOptions().disable();
		http.cors().and().csrf().disable()
			.anonymous().disable()
			.authorizeRequests()
//				.antMatchers(HttpMethod.POST, "/**").permitAll()
//				.antMatchers(HttpMethod.GET, "/**").permitAll()
//				.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
				.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()//让Spring security放行所有preflight request 
				.requestMatchers(CorsUtils::isCorsRequest).permitAll()//"/**","/login","/logout",
				.antMatchers("/getVerify","/lib/**","/ws/**","/swagger-ui.html","/doc.html", "/v2/**", "/webjars/**", "/swagger-resources/**").permitAll()
				.anyRequest().authenticated()
				.and()
			.exceptionHandling()
				.authenticationEntryPoint(myAuthenctiationEntryPointHandler)//未登录402
				.accessDeniedHandler(myAuthenctiationDeniedHandler)//无权访问403
				.and()
			.formLogin() // 定义当需要用户登录时候,转到的登录页面
				.successHandler(myAuthenctiationSuccessHandler)
				.failureHandler(myAuthenctiationFailureHandler)
				.permitAll()
				.and()
			.logout()
				.logoutSuccessHandler(myAuthenctiationLogoutSuccessHandler)//退出登陆200
				.deleteCookies("JSESSIONID")
				.permitAll()//退出
				.and()
			.requestCache().disable();//使退出前的操作请求缓存为空失效,但是并没有更改获取缓存路径并跳转的实现,避免登录后跳转到上一次操作嗯对全路径下而非主页
//			.sessionManagement()
//				.invalidSessionStrategy(mMyAuthenctiationInvalidSessionStrategy);//session到期101,此配置前端存在过期cookies时会一直提示会话过期,删除后才会提示未登录
		
		http.sessionManagement().maximumSessions(1).expiredSessionStrategy(myAuthenctiationSessionStrategy);//会话管理:用户仅允许一个登陆,踢出旧的登录
		http.addFilterBefore(loginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
		http.addFilterBefore(WebSecurityCorsFilter(), ChannelProcessingFilter.class); // 保证跨域的过滤器首先触发


	}

	@Bean
	public CorsFilter WebSecurityCorsFilter() throws Exception {
		CorsFilter filter = new CorsFilter();
        return filter;
    }
	
	@Bean
	public LoginAuthenticationFilter loginAuthenticationFilter() throws Exception {
        LoginAuthenticationFilter filter = new LoginAuthenticationFilter();
        filter.setAuthenticationFailureHandler(myAuthenctiationFailureHandler);
        filter.setAuthenticationSuccessHandler(myAuthenctiationSuccessHandler);
        filter.setAuthenticationManager(authenticationManagerBean());
        return filter;
    }
	
	//加入中间验证层,可实现自定义验证用户等信息
	@Bean
    public AuthenticationProvider authenticationProvider() {
        AuthenticationProvider provider = new MyAuthenticationProvider();
        return provider;
    }
	
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }

}

5.3、CorsFilter跨域过滤

跨域时返回到客户端的响应头必须有相应处理并返回,配置文件的配置并不会受理

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class CorsFilter implements Filter {

    Logger logger= LoggerFactory.getLogger(CorsFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }
    
    @Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request= (HttpServletRequest) servletRequest;
        HttpServletResponse response= (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin",request.getHeader("origin"));
        response.setHeader("Access-Control-Allow-Origin","http://192.168.52.26:3000");  //允许跨域访问的域
        response.setHeader("Access-Control-Allow-Origin","http://localhost:3000");  //允许跨域访问的域
        response.setHeader("Access-Control-Allow-Methods","POST,GET,OPTIONS,DELETE,PUT");  //允许使用的请求方法
        response.setHeader("Access-Control-Expose-Headers","Authorization");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with,Cache-Control,Pragma,Content-Type,Authorization");  //允许使用的请求方法
        response.setHeader("Access-Control-Allow-Credentials","true");//是否允许请求带有验证信息
        chain.doFilter(request, response);
	}
    
    @Override
    public void destroy() {

    }

}

5.4、UsernamePasswordAuthenticationFilter账户过滤

前后端分离时的json登录方式,解决获取不到用户名密码问题 security的登录默认是表单提交的,但是改成json后获取不到提交的数据,此时增加一个处理用户名密码转存饿得地方

import java.io.IOException;
import java.util.Map;

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

import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.fasterxml.jackson.databind.ObjectMapper;

public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {//必须post登录
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        //如果是application/json类型,做如下处理
        if(request.getContentType() != null && (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)||request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE))){
            //以json形式处理数据
            String username = null;
            String password = null;
            String vercode = null;

            try {
            	//将请求中的数据转为map
                Map<String,String> map = new ObjectMapper().readValue(request.getInputStream(), Map.class);
                username = map.get("username");
                password = map.get("password");
//                vercode = map.get("vercode");
                
//                request.getSession().setAttribute("vercode", vercode);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (username == null) {
                username = "";
            }
            if (password == null) {
                password = "";
            }
            if (vercode == null) {
            	vercode = "";
            }
            
            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);

            return this.getAuthenticationManager().authenticate(authRequest);
        }
        //否则使用官方默认处理方式
        return super.attemptAuthentication(request, response);
    }
}


六、postman测试不支持跨域

6.1登录成功

登录测试

6.2登录失败

在这里插入图片描述

6.3退出登录

在这里插入图片描述

6.4未登录

在这里插入图片描述

6.5无权访问

在这里插入图片描述

6.6会话到期

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

herozhi0821

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值