基于SpringSecurity新API并接收Json的SpringSecurity用户验证

关于使用JSON传输数据来实现SpringSecurity用户验证的解决方案

前情摘要

  1. 本方案是解决WebConfigrationAdapter过时,利用新API解决的方案

  2. 本方案是解决传统表单提交数据,改用JSON传输数据

  3. 本方案使用了官方推荐的Bcytpe加密算法,将存储用户密码,解密,都使用了以上加密算法

  4. 最后,希望本套解决方案能够帮助您在使用SpringSecurity中解决一些问题

参考文章:【深入浅出Spring Security(五)】自定义过滤器进行前后端登录认证

ps:上面的大佬关于SpringSecurity有着深厚的理解,大家可以去学习学习


我们就废话不多说,开始我们的解决方案

我们需要干些什么?

1.引入依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter-test</artifactId>
            <version>2.3.1</version>
            <scope>test</scope>
        </dependency>
        <!--jwt依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <!--德鲁伊数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.18</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
        <!--spring boot web模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
     <!--配置文件提示-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
     <!--SpringSecurity依赖-->
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2.关于springSecurity老的表单认证

我们需要知道,老的表单认证,我们是基于UsernamePasswordFilter这个过滤器的

那我们需要做什么:新创建一个类去继承UsernamePasswordFilter,然后重写里面的方法 ——attemptAuthentication(HttpServletRequest request, HttpServletResponse response)

JsonUsernamePasswordFilter.java

package com.hitBadminton.security;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

public class JsonUsernamePasswordFilter extends UsernamePasswordAuthenticationFilter {
    @Autowired
    ObjectMapper objectMapper;

    public JsonUsernamePasswordFilter() {
    }

    public JsonUsernamePasswordFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        // 判断请求方式是否是 POST 方式
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        // 然后判断是否是 JSON 格式的数据
        if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
            // 如果是的话就从 JSON 中取出用户信息进行认证
            try {
                //使用jackson读取json
                Map<String, String> userInfo = objectMapper.readValue(request.getInputStream(), Map.class);
                String username = userInfo.get(getUsernameParameter());
                String password = userInfo.get(getPasswordParameter());
                // 封装成Authentication
                UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
                // 仿造父类去调用AuthenticationManager.authenticate认证就可以了
                setDetails(request, authRequest);
                return getAuthenticationManager().authenticate(authRequest);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        // 否则用父类的方式去认证
        return super.attemptAuthentication(request, response);
    }
}


3.在springSecurity的配置类中将这个JSONUsernamePasswordFilter替换掉原有的UsernamePasswordFilter

以前我们需要自己新建一个类去继承WebConfigurationAdepter

like this :

public class WebConfig extends WebConfigurationAdepter {
   
}

但是现在springSecurity升级过后,将 WebConfigrationAdepter 标注 WebConfigrationAdepter ,那么这个方法就过时了。

那我们就使用新的API去配置SpringSecurity的配置类

WebSecurityConfig.java

package com.hitBadminton.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.hitBadminton.security.JsonUsernamePasswordFilter;
import com.hitBadminton.security.UserDetailsServiceImpl;
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.authentication.AuthenticationManager;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.WebSecurityConfiguration;

import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
//@Configuration 可标可不标
//因为@EnableWebSecurity 已经包含了@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
    @Autowired
    ObjectMapper objectMapper;
    @Autowired
    UserDetailsServiceImpl userDetailsService;
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return web -> web.ignoring().antMatchers(HttpMethod.POST,"/user");
    }
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .logout()
                .logoutSuccessHandler(this::onAuthenticationSuccess)
                .logoutUrl("/logout")
                .and()
            	//.addFilterAt 替换掉目标类
                .addFilterAt(JsonUsernamePasswordFilter(http), UsernamePasswordAuthenticationFilter.class)
                .csrf()
                .disable()
                .build();
    }

    /**
     * 自定义 AuthenticationManager
     *
     * @param http
     * @return AuthenticationManager
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.
                getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userDetailsService)
                .and()
                .build();
    }

    @Bean
    public JsonUsernamePasswordFilter JsonUsernamePasswordFilter(HttpSecurity http) throws Exception {
        JsonUsernamePasswordFilter filter = new JsonUsernamePasswordFilter(authenticationManager(http));
        // 自定义 JSON 的 key
        filter.setUsernameParameter("username");
        filter.setPasswordParameter("password");
        // 自定义接收的url,默认是login
        // 此过滤器的doFilter是在AbstractAuthenticationProcessingFilter,在那里进行的url是否符合的判定
        filter.setFilterProcessesUrl("/user/login");
        // 设置login成功返回的JSON数据
        filter.setAuthenticationSuccessHandler(this::onAuthenticationSuccess);
        // 设置login失败返回的JSON数据
        filter.setAuthenticationFailureHandler(this::onAuthenticationFailure);
        return filter;
    }

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        if (request.getRequestURI().endsWith("/login"))
            out.write(objectMapper.writeValueAsString("登录成功"));
        else if (request.getRequestURI().endsWith("/logout"))
            out.write(objectMapper.writeValueAsString("注销成功"));
        out.close();
    }

    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write(objectMapper.writeValueAsString("登录失败"));
        out.close();
    }
    /**
     * 向容器注入passwordEncoder进行加密
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();//官方推荐使用BCryptPasswordEncoder,更安全!!!
    }
}

4.接下来去实现我们的UserDetailsService

UserDetailsServiceImpl

package com.hitBadminton.security;


import com.hitBadminton.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.stereotype.Service;

import java.util.List;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (userService.getUserDetail(username) == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        com.hitBadminton.pojo.User user = userService.getUserDetail(username);
        String encodePassword = user.getUserPassword();
        //这里demo随便写的权限list
        List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        return new User(user.getUserId(), encodePassword, list);
    }

}

PasswordUtils

package com.hitBadminton.utills;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class PasswordUtils {
   public static BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    public static String encodePassword(String rawPassword) {
        return bCryptPasswordEncoder.encode(rawPassword);
    }
}

接下来的操作都大同小异,你们就自己实现啦~

注意事项

  1. 我们在使用Bcypt加密需要将password字段长度设置至少大于60
public class PasswordUtils {
   public static BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    public static String encodePassword(String rawPassword) {
        return bCryptPasswordEncoder.encode(rawPassword);
    }
}

接下来的操作都大同小异,你们就自己实现啦~

注意事项

  1. 我们在使用Bcypt加密需要将password字段长度设置至少大于60

测试效果

1.登录(username & password == true)
在这里插入图片描述
2.登录(username & password != true)
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值