因为 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应用:https://www.cnblogs.com/ye-hcj/p/9632694.html
Spring Boot之拦截器与过滤器(完整版) :https://www.cnblogs.com/yifeiyaoshangtian/p/10280808.html