Bootstrap

spring security6重写登陆验证

  1. spring security的认证流程
    在这里插入图片描述

在这里插入图片描述
2. 从文档上可以看出来,UsernamePasswordAuthenticationFilter和AuthenticationManager是认证的关键步骤,/login传过来的username和password由UsernamePasswordAuthenticationFilter接收处理成UsernamePasswordAuthenticationToken,再传给AuthenticationManager,在AuthenticationManager中判断密码正确与否
3. 代码实现
(一)、登录部分:登录可以使用两种方式进行登陆
第一种:只用spring security默认提供的/login接口进行登录(这种方式需要修改spring security默认的登录设置,比较办法)
第二种:自己写登陆的controller(这种比较简单就不研究了)

第一步 WebSecurityConfig配置文件

package com.xuhao.springsecuritycom.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.xuhao.springsecuritycom.filters.CustomUsernamePasswordAuthenticationFilter;
import com.xuhao.springsecuritycom.filters.TokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableWebSecurity
// 开启注解模式,否则权限注解会无效
@EnableGlobalMethodSecurity(
        prePostEnabled = true,  // 启用@PreAuthorize和@PostAuthorize注解
        securedEnabled = true   // 启用@Secured注解)
)
public class SecurityConfig {
    @Autowired
    private TokenFilter tokenFilter;
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeHttpRequests((requests) -> requests
                        .requestMatchers(HttpMethod.POST,"/user/create", "/login").permitAll()
                        .anyRequest().authenticated()
                )
                .logout((logout) -> logout.permitAll());
        // 设置无权访问时的返回
        httpSecurity.exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler());
        // 设置未登录,访问受保护地址的拦截
        httpSecurity.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint());
        // 配置token拦截过滤器,这个过滤器很重要,用来判断用户是否登录的逻辑
        httpSecurity.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 关闭csrf,否则ajax无法通过拦截
        httpSecurity.csrf().disable();
        // 不通过Session获取SecurityContext
        httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        return httpSecurity.build();
    }
    /**
     * 设置加密规则
     * PasswordEncoderFactories.createDelegatingPasswordEncoder()
     * 创建的DelegatingPasswordEncoder会自动根据配置的密码存储类型来选择合适的加密规则
     * */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
    /**
     * 这是登录过滤器的bean
     */
    @Bean
    public CustomUsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() {
        CustomUsernamePasswordAuthenticationFilter loginFilter = new CustomUsernamePasswordAuthenticationFilter();
        loginFilter.setAuthenticationManager(customAuthenticationManager());
        return loginFilter;
    }

    // 设置判断登录成功的实现类的bean
    @Bean
    public CustomAuthenticationManager customAuthenticationManager() {
        return new CustomAuthenticationManager();
    }

}


第二步 重写登录接口/login的逻辑

package com.xuhao.springsecuritycom.filters;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 重写登录拦截过滤器
 * */
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    // 接收请求参数
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        String username = this.obtainUsername(request);
        username = username != null ? username.trim() : "";
        String password = this.obtainPassword(request);
        password = password != null ? password : "";
        UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
        this.setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
    // 设置/login接口登录成功以后得接口返回
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        Map<String,Object> map = new HashMap<>();
        map.put("code",200);
        map.put("msg","登陆成功");
        // 获取用户信息这里也可以把生成的token返回给前端,写死token懒得接jwt了,后面就判断token=1就是登录了
        map.put("token","1");
        map.put("authentication",authResult);
        String string = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(string);
    }
    // 设置/login接口登录失败以后得接口返回
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        Map<String,Object> map = new HashMap<>();
        map.put("code",302);
        map.put("msg","登陆失败");
        String string = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(string);
    }
}


第三步 重写AuthenticationManager,用于判断密码是否正确

package com.xuhao.springsecuritycom.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.ArrayList;
import java.util.Collection;

public class CustomAuthenticationManager  implements AuthenticationManager {
    @Autowired
    PasswordEncoder passwordEncoder;
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 从传入的 Authentication 对象中获取用户提供的凭证信息,比如用户名和密码
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        // 写死密码123456用户名xuhao,开发时应该是从数据库获取到的密码
        String contrastPaw = passwordEncoder.encode("123456");
        // 模拟一个权限列表
        Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_ADD"));

        if(username.equals("xuhao") && passwordEncoder.matches(password, contrastPaw)) {
            return new UsernamePasswordAuthenticationToken(username, password, authorities);
        } else {
            throw new BadCredentialsException("Invalid username or password");
        }
    }
}


第四步 重写未登录,未授权的异常返回

package com.xuhao.springsecuritycom.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
 * 未授权访问拦截
 * */
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        Map<String,Object> map = new HashMap<>();
        map.put("code",403);
        map.put("msg","您无权访问此地址");
        String string = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(string);
    }
}
package com.xuhao.springsecuritycom.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 未登录返回拦截
 * */
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        Map<String,Object> map = new HashMap<>();
        map.put("code",401);
        map.put("msg","请登录后访问");
        String string = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(string);
    }
}

第五步 写测试接口

package com.xuhao.springsecuritycom.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/user")
public class User {
    @Autowired
    PasswordEncoder encoder;
    @PostMapping("/create")
    public ResponseEntity<?> createUser(@RequestParam String userName, @RequestParam String password) {
        Map<String, String> map = new HashMap<>();
        map.put("userName", userName);
        map.put("password", encoder.encode(password));
        System.out.println(encoder.matches("123456", encoder.encode(password)));
        return ResponseEntity.ok().body(map);
    }


    @GetMapping("/see")
    // 测试测试权限,全局写死权限为ROLE_ADD,所以访问SEE时就会被权限过滤器拦截
    @Secured("ROLE_SEE")
    public ResponseEntity<?> seeUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return ResponseEntity.ok().body(authentication);
    }
}

附上源码地址: spring security6示例代码

;