springboot集成spring-security实现httpbasic

万俟华辉
2023-12-01

原文档位于有道云中,可以更好的浏览

文档:spring-security使用.note
链接:http://note.youdao.com/noteshare?id=56c0fc9382b5dc19f69f6874adfaf905&sub=5086D29C26684C14B769F1AD14529C50

一、 相关概念说明
1、Spring Security
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
2、HttpBasic

a、首先,HttpBasic模式要求传输的用户名密码使用Base64模式进行加密。如果用户名是 “admin” ,密码是“ admin”,则将字符串"admin:admin"使用Base64编码算法加密。加密结果可能是:YWtaW46YWRtaW4=。
b、然后,在Http请求中使用Authorization作为一个Header,“Basic YWtaW46YWRtaW4=“作为Header的值,发送给服务端。(注意这里使用Basic+空格+加密串)
c、服务器在收到这样的请求时,到达BasicAuthenticationFilter过滤器,将提取“ Authorization”的Header值,并使用用于验证用户身份的相同算法Base64进行解码。
d、解码结果与登录验证的用户名密码匹配,匹配成功则可以继续过滤器后续的访问。

所以,HttpBasic模式真的是非常简单又简陋的验证模式,Base64的加密算法是可逆的,你知道上面的原理,分分钟就破解掉。我们完全可以使用PostMan工具,发送Http请求进行登录验证。
二、实现过程
1、配置用户名与密码:
security.basic.basic-user-info[0].username=test1
security.basic.basic-user-info[0].password=abc123
security.basic.basic-user-info[0].roles[0]=ADMIN
security.basic.basic-user-info[0].roles[1]=USER
security.basic.basic-user-info[1].username=test2
security.basic.basic-user-info[1].password=abc123
security.basic.basic-user-info[1].roles[0]=USER

自定义对应的用户类
package com.sitech.gsgc.security.model;

import lombok.Data;
import lombok.ToString;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.util.*;

/**

  • @author: renzj

  • @date: 2022-06-16

  • @description: security认证的信息对象

  • @version: 1.0
    */
    @Data
    @ToString
    public class SecurityUserInfo {

    private String username;
    private String password;
    private String[] roles;
    }

添加从配置中获取内容
package com.sitech.gsgc.security.model;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

/**

  • @author: renzj

  • @date: 2022-06-16

  • @description: 从配置文件中获取配置的用户名与密码等信息

  • @version: 1.0
    */
    @Component
    @ConfigurationProperties(prefix = “security.basic”)
    public class SecurityBasicProperties {
    private List securityUserInfos;

    public List getSecurityUserInfo() {
    return securityUserInfos;
    }

    public void setBasicUserInfo(List basicUserInfo) {
    this.securityUserInfos = basicUserInfo;
    }
    }

2、拦截配置类
package com.sitech.gsgc.security.basic;

import com.sitech.gsgc.security.model.SecurityBasicProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

/**

  • @author: renzj

  • @date: 2022-06-20

  • @description: spring-security配置类,

  • @version: 1.0
    */
    @EnableWebSecurity
    @Configuration
    @Profile(“secure-basic-web”)
    // @EnableConfigurationProperties(SecurityBasicProperties.class)
    public class GsgcWebSecurityConfig extends WebSecurityConfigurerAdapter {
    public static String REALM = “BASIC_GSGC”;

    //用来指定从何处获取用户
    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(getUserDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
    }

    //注入自定义的用户认证逻辑
    @Bean
    public UserDetailsService getUserDetailsService() {
    return new GsgcUserDetailsService();
    }

    //开启跨域
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.applyPermitDefaultValues();
    configuration.addAllowedMethod(“*”);
    configuration.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration(“/**”, configuration);
    return source;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    //csrf是防伪造攻击,如果开启则我们没办法用postman来测试,必须关掉
    http.csrf().disable()
    //所有方式的请求都要通过验证
    .authorizeRequests()
    //放行security验证接口
    .antMatchers(“/testSecurity”).permitAll()
    // .antMatchers(“/upload”).permitAll()
    .anyRequest().hasRole(“ADMIN”)
    //添加自定义的403返回内容
    .and().exceptionHandling().accessDeniedHandler(myAccessDeniedHandler())
    //添加自定义的401返回内容
    .and().httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint())
    //关闭session
    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//We don’t need sessions to be created.
    }

    @Bean
    public GsgcAuthenticationEndPoint getBasicAuthEntryPoint() {
    return new GsgcAuthenticationEndPoint();
    }

    @Bean
    public GsgcAccessDeniedHandler myAccessDeniedHandler() {
    return new GsgcAccessDeniedHandler();
    }

}

3、自定义异常类
401异常类
package com.sitech.gsgc.security.basic;

import cn.hutool.json.JSONUtil;
import com.sitech.gsgc.service.support.ServerResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**

  • @author renzj

  • @version 1.0

  • @date 2020-09-24 10:36

  • 针对没登录的请求到来进行统一处理
    */
    public class GsgcAuthenticationEndPoint extends BasicAuthenticationEntryPoint {

    @Override
    public void commence(final HttpServletRequest request,
    final HttpServletResponse response,
    final AuthenticationException authException) throws IOException {
    //Authentication failed, send error response.
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.addHeader(“WWW-Authenticate”, “Basic realm=” + getRealmName() + “”);
    // PrintWriter writer = response.getWriter();
    // writer.println("HTTP Status 401 : " + authException.getMessage());
    response.setContentType(“application/json;charset=utf-8”);
    ServerResponse error = ServerResponse.createError(“HTTP Status 401:”+authException.getMessage());
    String jsonStr = JSONUtil.toJsonStr(error);
    response.getWriter().write(jsonStr);
    response.getWriter().flush();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
    setRealmName(GsgcWebSecurityConfig.REALM);
    super.afterPropertiesSet();
    }
    }

403异常类
package com.sitech.gsgc.security.basic;

import cn.hutool.json.JSONUtil;
import com.sitech.gsgc.service.support.ServerResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**

  • @author renzj
  • @version 1.0
  • @date 2020-09-24 10:24
  • 针对已登录用户因为权限不够而被拒绝访问的处理器
    */
    public class GsgcAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
    response.setContentType(“application/json;charset=utf-8”);
    ServerResponse error = ServerResponse.createError(“HTTP Status 403:”+accessDeniedException.getMessage());
    String jsonStr = JSONUtil.toJsonStr(error);
    response.getWriter().write(jsonStr);
    response.getWriter().flush();
    }

}

4、自定义用户验证过程
package com.sitech.gsgc.security.basic;

import com.sitech.gsgc.security.model.SecurityBasicProperties;
import com.sitech.gsgc.security.model.SecurityUserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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.util.ObjectUtils;

import java.util.*;

/**

  • @author: renzj

  • @date: 2022-06-16

  • @description: 继承security中的用户密码验证规则,实现自己的逻辑

  • @version: 1.0
    */
    public class GsgcUserDetailsService implements UserDetailsService {

    //获取配置信息中的用户信息
    @Autowired
    private SecurityBasicProperties basicProperties;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    UserDetails userDetails = null;
    List securityUserInfo = basicProperties.getSecurityUserInfo();
    //利用并行流处理数据
    Optional first = securityUserInfo.parallelStream().filter(item -> item.getUsername().equals(username)).findFirst();
    //当验证没有当前用户时,提示报错
    if (!first.isPresent()) {
    throw new BadCredentialsException(“User '” + username + “’ not found”);
    }
    SecurityUserInfo userInfo = first.get();
    Collection authList = getAuthorities(userInfo.getRoles());
    userDetails = new User(userInfo.getUsername(), new BCryptPasswordEncoder().encode(userInfo.getPassword()), true, true, true, true, authList);
    return userDetails;
    }

    private Collection getAuthorities(String[] roles) {
    List authList = new ArrayList();
    if (ObjectUtils.isEmpty(roles) || roles.length == 0) {
    authList.add(new SimpleGrantedAuthority(“ROLE_USER”));
    } else {
    Arrays.stream(roles).forEach(role -> {
    authList.add(new SimpleGrantedAuthority(“ROLE_” + role));
    });
    }
    return authList;
    }
    }

5、测试样例
调用成功
{
“resCode”: “0000”,
“resMsg”: “The test Result is{“resCode”:“0000”,“resMsg”:“The gsgc application is healthy”}”
}

调用401
{
“resCode”: “0001”,
“resMsg”: “HTTP Status 401:Bad credentials”
}

调用403
{
“resCode”: “0001”,
“resMsg”: “HTTP Status 403:Access is denied”
}

用户验证异常
{
“resCode”: “0001”,
“resMsg”: “HTTP Status 401:Invalid basic authentication token”
}

 类似资料: