贫民窟里的程序高手

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

因为 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

posted on 2021-08-20 11:33  贫民窟里的程序高手  阅读(967)  评论(0)    收藏  举报