Springboot + Spring Security 前后端分离权限控制
前言
最近因为领导要求,要把现在项目改成前端段分离的形式,因此,本文不再具体讲述Spring Security中如何实现用户动态权限认证以及带有图片验证码的自定义认证部分。所以适合对Security有一定了解的伙伴看一下。
看了一些帖子实现方式都一样,只是在于权限的认证不同而已,所以本文基于已经实现security登录认证及权限控制,主要针对登陆成功、登录失败、退出登录、未登录、无权访问、会话到期这几部分进行拦截处理并返回json字符串。
简单的说,只需要在security配置文件中,对应位置的跳转URL配置改为拦截处理而已,因此只需要有六个拦截实现类以及修改六处配置,就都OK了。自己原有的其他逻辑不受影响。
至于其他具体实现细节,根据读者自己的实际需求添加吧。
代码Git
在此先声明一下,验证码验证部分存在问题,可以看我的另一篇,已经把验证码部分修改为过滤器处理。
一、前期准备
1.1 统一的返回实体类
只是为了统一返回json格式,可以不用。
本文使用的json包为com.alibaba.fastjson,需要注意的是使用JSON或JSONObject转换的时候效果是一样的,不同的是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);
}
}