springboot2.0.x 集成security

最近在接触安全模块,对于所涉及到的知识进行梳理总结,以下是我最近梳理的结果,构建思维导图,更直观的展示出所有注意到的模块。

 

框架源码 -- springboot security_ide

上代码:自己撸(项目源码)

​https://github.com/shimingda/security.git​

项目架构

项目采用springboot2.0.2作为技术架构,需要使用权限角色处理,通过考察选取集成spring security框架。

主要依赖

spring-boot-starter-web
spring-boot-starter-security
spring-boot-starter-aop
spring-boot-starter-jdbc
spring-boot-starter-data-jpa
spring-security-oauth2
org.springframework.security
spring-boot-starter-redis
lombok
fastjson
logback

spring security config

import com.dome.authenticate.AuthenticateProvider;
import com.dome.authenticate.MyCustomUserService;
import com.dome.config.properties.SecurityProperties;
import com.dome.handler.MerryyouAuthenticationfailureHandler;
import com.dome.handler.MerryyouLoginSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;

/**
* 核心配置文件
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private MyCustomUserService userService;

@Autowired
private SecurityProperties securityProperties;

@Autowired
private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;

@Autowired
private MerryyouLoginSuccessHandler myAuthenticationSuccessHandler;

@Autowired
private MerryyouAuthenticationfailureHandler myAuthenticationFailHandler;

@Autowired
private SessionRegistry sessionRegistry;

@Autowired
private InvalidSessionStrategy invalidSessionStrategy;
@Autowired
public void globalConfigure(AuthenticationManagerBuilder auth){
auth.authenticationProvider(authenticateProvider());
}
// Remove the ROLE_ prefix
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("");
}
@Bean
public AuthenticateProvider authenticateProvider() {
AuthenticateProvider provider = new AuthenticateProvider();
provider.setUserDetailsService(userService);
provider.setPasswordEncoder(new BCryptPasswordEncoder());
return provider;
}

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
//权限控制
http
.authorizeRequests()
.antMatchers("/user/save","/session/*").permitAll()
.antMatchers("/test/role").hasAnyRole("admin")
.antMatchers("/test/permission").hasRole("super_admin")
.antMatchers("/user/permission").access("super_admin")
.anyRequest().authenticated();
//session管理
http
.sessionManagement()
.invalidSessionStrategy(invalidSessionStrategy)//session失效策略处理
.maximumSessions(securityProperties.getSession().getMaximumSessions())//最大session并发数量1
.maxSessionsPreventsLogin(securityProperties.getSession().isMaxSessionsPreventsLogin())//之后的登录踢掉之前的登录
.expiredSessionStrategy(sessionInformationExpiredStrategy)//并发过期处理
.sessionRegistry(sessionRegistry)
;
//http缓存
http
.requestCache()
.requestCache(new HttpSessionRequestCache());
//登录验证配置post验证
http
.formLogin()
.loginProcessingUrl("/user/login2")
.usernameParameter("username")
.passwordParameter("password")
// 自定义的登录验证成功或失败后的去向
.successHandler(myAuthenticationSuccessHandler)
// .successHandler(appLoginInSuccessHandler)
.failureHandler(myAuthenticationFailHandler);
// 安全退出用户
http
.logout()
.logoutUrl("signOut").permitAll()
.deleteCookies("")
.invalidateHttpSession(true)
.logoutSuccessUrl("/");
// 禁用csrf防御机制(跨域请求伪造),这么做在测试和开发会比较方便。
http
.csrf().disable();
// token管理
// http
// .rememberMe()
// .tokenValiditySeconds(securityProperties.getRememberMeSeconds())
// .userDetailsService(userService);
http.httpBasic();
}
}

项目集成考察

下面是对项目集成进行考察内容,大家有兴趣可以看一下。如果有人问起,可以说出根据,以免被人问的措手不及。

框架源码 -- springboot security_java_02

用户验证模块

框架源码 -- springboot security_spring_03

用户验证,主要是登录验证,其中可以集成形式,但是在代码中没有去过多实现,因为暂时没有需要去进行集成。如果后续涉及,在进行添加。

认证核心代码

import com.dome.entity.SysUser;
import com.dome.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;

@Component
public class MyCustomUserService implements UserDetailsService {
/**
* 登陆验证时,通过username获取用户的所有权限信息
* 并返回UserDetails放到spring的全局缓存SecurityContextHolder中,以供授权器使用
*/
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) {
// 获取当前的用户
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
SysUser user=null;
if (null!=authentication){
user= (SysUser) authentication.getPrincipal();
}
if(null==user){
user=userService.getUserByName(username);
if (null==user) {
throw new BadCredentialsException("用户不存在");
}
}
return user;
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.Collection;

/**
* 认证
*/
@Component
public class AuthenticateProvider extends DaoAuthenticationProvider {

@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
UserDetails user = this.getUserDetailsService().loadUserByUsername(username);

if (!user.getPassword().equals(passwordEncoder.encode(password))){
throw new BadCredentialsException("用户名密码不匹配");
}
if (user.isEnabled()) {
throw new BadCredentialsException("用户被禁用");
}
Collection<? extends GrantedAuthority> grantedAuthorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(user, password,grantedAuthorities);
}

@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
super.additionalAuthenticationChecks(userDetails, authentication);
if (!userDetails.isEnabled()) {
throw new DisabledException("账户被禁用,请联系管理员");
}
}
}

建议

这部分入手首先去了解用户登录到验证的流程,这种流程梳理网上很多,看一看就能抓住里面的核心是什么,在进行代码编写会轻松很多。

注意

用户信息保密,对于密码要加密处理。加密方式多种,考核适合的进行集成

token根据业务需求进行设置,进行不要太长,如果用户过多需要考虑内存性能问题。

集成OAuth2

框架源码 -- springboot security_ide_04

项目有需要可以去尝试集成OAuth2,在分享的项目中,已集成github第三方登录。可尝试QQ,微信,微博等内容,需要申请授权,过程耗时,没有去实现。

做这个坑很多,建议先看一下​​理解OAuth 2.0 - 阮一峰的网络日志​​这篇博客,讲的非常好。

请求授权访问时有坑要注意:

框架源码 -- springboot security_java_05

框架源码 -- springboot security_ide_06

权限角色处理

会话管理

框架源码 -- springboot security_spring_07

请求监控

框架源码 -- springboot security_spring_08

防止暴力访问代码

简单实现功能,达到防止暴力访问即可

import com.dome.redis.CacheUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.server.Session;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
/**
* 预防暴力访问和记录url访问信息
*
*/
@Aspect
@Component
public class HttpAspect {

private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);
Map<String,LinkedList<Long>> apiMap=new HashMap<>();
LinkedList<Long> timeList=null;
@Pointcut("execution(public * com.dome.controller.*.*(..))")
public void log() {
System.out.println(123);
}
//todo
//现在存放在session中,应该放在redis
@Before("log()")
public void doBefore(JoinPoint joinPoint) throws Exception {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

HttpServletRequest request = attributes.getRequest()
String sessionId=request.getSession().getId();
StringBuffer url =request.getRequestURL();
url.append(request.getMethod()).append(sessionId);

timeList= CacheUtils.getBean(url.toString(),LinkedList.class);
if(timeList==null||timeList.isEmpty()){
timeList=new LinkedList<>();
}
Long createTime = System.currentTimeMillis();
timeList.add(createTime);

int count=timeList.size();
if(count>10){
Long lastTime=timeList.getLast();
Long last2Time=timeList.get(count-1);
System.out.println(lastTime);
System.out.println(last2Time);
if(lastTime-last2Time<3000){
Long firstTime=timeList.getFirst();
System.out.println(firstTime);
if(lastTime-firstTime<30000){
throw new Exception("调用太过频繁");
}
}
}
logger.info("url:{}访问次数为count={}}", request.getRequestURL(), count);
//url method ip
logger.info("利用AOP记录每次请求的有关信息,url={},method={},ip={}", request.getRequestURL(), request.getMethod(), request.getRemoteAddr());
//类方法 参数
logger.info("class_method={}, args={}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(), joinPoint.getArgs());
CacheUtils.saveBean(url.toString(),timeList);
}

@After("log()")
public void doAfter() {

}

@AfterReturning(returning = "object", pointcut = "log()")
public void doAfterReturning(Object object) {

}

}

异常处理

框架源码 -- springboot security_spring boot_09

上代码:自己撸(项目源码)

​https://github.com/shimingda/security.git​