因为 Basic Auth 的身份信息是写在请求中,被截获账号密码可能会泄露,为此增加一重ip认证
在实际应用中,可能会用spring boot 写一些微服务去做底层的一些预处理,然后再开放一些接口传输数据。为了安全,同城要做一些访问的认证,也不用选太复杂的认证方式,就用 Basic Auth就可以,再在此基础上再做一些认证,比如这里的ip。
为此,需要两个方面的思考
1、如何做 Basic Auth 的认证
2、如何检验访问者的ip并授权
下面通过代码说明
一、依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
二、控制器Controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
/**
* api
*/
@RestController
@RequestMapping("/translate")
public class TranslateController {
@ResponseBody
@RequestMapping(value = "/AuthTest", method = RequestMethod.GET)
public String AuthTest() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.getName());
return "OK";
}
}
三、匿名用户访问无权限资源时的异常处理 类
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 匿名用户访问无权限资源时的异常处理
* 重写commence,处理异常
* 当 认证失败时 会跳转到 commence 方法,所以这里可以做一些定制化
*/
@Component
public class Authenication extends BasicAuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx) throws IOException {
response.addHeader("WWW-Authenticate", "Basic realm=" + getRealmName());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("账号密码不正确 HTTP Status 401 - " + authEx.getMessage());
}
@Override
public void afterPropertiesSet() {
setRealmName("translate");
super.afterPropertiesSet();
}
}
四、web 安全认证配置 类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {
@Value("${myname}")
private String myname;
@Value("${mypassword}")
private String mypassword;
private final static Logger log = LoggerFactory.getLogger(WebSecurityConfig.class);
@Autowired
private AuthenticationEntryPoint authEntryPoint;
@Override
protected void configure(HttpSecurity http) throws Exception {
// 关闭跨域保护
http.cors().and().csrf().disable();
// 所有的请求都要验证
http.authorizeRequests().anyRequest().authenticated();
// 使用authenticationEntryPoint验证 user/password
http.httpBasic().authenticationEntryPoint(authEntryPoint);
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
/**
* 配置授权的 账号密码
* 这里是在配置文件配置好
*
* @param auth
* @throws Exception
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
log.info("user: " + myname);
log.info("password: " + mypassword);
String encrytedPassword = this.passwordEncoder().encode(mypassword);
System.out.println("Encoded password = " + encrytedPassword);
// 这里使用写死的验证
InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> mngConfig = auth.inMemoryAuthentication();
UserDetails u1 = User.withUsername(myname).password(encrytedPassword).roles("ADMIN").build();
mngConfig.withUser(u1);
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST", "PUT", "DELETE").allowedOrigins("*")
.allowedHeaders("*");
}
}
五、配置文件 application.yml
server:
port: 9999
servlet:
context-path: /translate-web/
#请求账号密码
myname: test
mypassword: 123456
#授权ips,逗号隔开
ipAuthSwitch: true
ips: 192.168.1.2,0:0:0:0:0:0:0:1
六、postman 访问(带上认证信息)
至此,整个Basic Auth认证就完成了
下面我们在上面的基础上补充ip认证
原理就是用拦截器拦截请求,然后在请求中获取ip,将这个ip和配置授权的ip做对比,符合就通过,否则不允许请求
七、自定义拦截器
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Set;
/**
* 拦截器
*/
public class TranslateInterceptor implements HandlerInterceptor {
private final static Logger log = LoggerFactory.getLogger(TranslateInterceptor.class);
long start = System.currentTimeMillis();
private Set<String> ips;
private Boolean ipAuthSwitch;
public TranslateInterceptor( Set<String> ips, Boolean ipAuthSwitch) {
this.ips = ips;
this.ipAuthSwitch = ipAuthSwitch;
}
/**
* preHandle是在请求执行前执行的
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
start = System.currentTimeMillis();
String ip = request.getRemoteAddr();
log.info("request ip: " + ip);
/**
* 返回true,postHandler和afterCompletion方法才能执行
* 否则false为拒绝执行,起到拦截器控制作用
*/
if (ipAuthSwitch) {
if(StringUtils.isNotEmpty(ip) && ips.contains(ip)){
return true;
}else{
log.info("ip:{} No authority", ip);
return false;
}
}else{
return true;
}
}
/**
* postHandler是在请求结束之后,视图渲染之前执行的,但只有preHandle方法返回true的时候才会执行
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
System.out.println("Interception cost=" + (System.currentTimeMillis() - start));
}
/**
* afterCompletion是视图渲染完成之后才执行,同样需要preHandle返回true
*
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
//该方法通常用于清理资源等工作
}
}
八、拦截器配置
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* 拦截器配置
*/
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Value("${ips}")
private String ips;
@Value("${ipAuthSwitch}")
private Boolean ipAuthSwitch;
@Override
public void addInterceptors(InterceptorRegistry registry) {
String[] split = ips.split(",");
Set<String> ipSet = new HashSet<>(Arrays.asList(split));
registry.addInterceptor(new TranslateInterceptor(ipSet, ipAuthSwitch))
//添加需要验证登录用户操作权限的请求
.addPathPatterns("/**")
//这里add为“/**”,下面的exclude才起作用,且不管controller层是否有匹配客户端请求,拦截器都起作用拦截
//排除不需要验证登录用户操作权限的请求
.excludePathPatterns("/wang")
.excludePathPatterns("/css/**")
.excludePathPatterns("/js/**")
.excludePathPatterns("/images/**");
//这里可以用registry.addInterceptor添加多个拦截器实例,后面加上匹配模式
super.addInterceptors(registry);//最后将register往这里塞进去就可以了
}
}
最后感谢两位博主的资料
springboot成神之——Basic Auth应用:springboot成神之——Basic Auth应用 - qz奔跑的马 - 博客园
Spring Boot之拦截器与过滤器(完整版) :Spring Boot之拦截器与过滤器(完整版) - 一飞要上天 - 博客园