欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-10 10:41:34
...

前言

最近因为领导要求,要把现在项目改成前端段分离的形式,因此,本文不再具体讲述Spring Security中如何实现用户动态权限认证以及带有图片验证码的自定义认证部分。所以适合对Security有一定了解的伙伴看一下。
本文基于已经实现security登录认证及权限控制,主要针对登陆成功登录失败退出登录未登录无权访问会话到期这几部分进行拦截处理。
至于其他具体实现细节,根据读者自己的实际需求添加吧。
代码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;
    }
}

五、postman测试

5.1登录成功

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

5.2登录失败

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

5.3退出登录

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

5.4未登录

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

5.5无权访问

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

5.6会话到期

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

相关标签: springsecurity java