Spring Security (二):自定义登陆

目录

前言

引入依赖

原理解析

最佳实践

自定义AuthenticationFilter

自定义AuthenticationManager

自定义Provider

自定义UserDetailsService

自定义用户信息

自定义认证成功handler

配置SecurityConfig

项目验证


前言

        Spring Security作为当下最流行的认证授权框架,提供了非常全面且完善的认证授权流程,并且通过与Springboot的结合,极大的简化了开发与使用。spring官方也提供了详细的文档和丰富的应用实例。

官方文档:Spring Security

应用实例:https://github.com/thombergs/code-examples/tree/master/spring-security

        但在我们的实际开发工作中,我们绝大部分都是前后端分离的项目,作为后端开发人员,我们并不关心前段页面的跳转,此外,大多数前后端业务数据都是通过JSON方式传递的,并不是Spring security 默认的form 格式。以下便是一个简单的通过JSON方式自定义登陆认证的项目简介。

引入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

原理解析

        spring security 提供了一个实现了DelegatingFilterProxy的Filter,使Servlet container 的生命周期和 Spring’s ApplicationContext建立联系,从而达到通过Filter对web请求进行授权认证,具体流程如下图所示:

delegatingfilterproxy

securityfilterchain

在spring security中,所有的filter都被放在SecurityFilterChain中 按照顺序被调用处理web request.

具体的源码解析可以参考:Spring Security (一):自定义登陆_见面说Hello的博客-CSDN博客

因此,要想实现自定义登陆认证,需要自定义Filter,具体项目代码如下所示。

最佳实践

自定义AuthenticationFilter

通过JSON方式获取用户名密码

public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {


    public CustomUsernamePasswordAuthenticationFilter() {
        super();
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST") || (!request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE))) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        // 2. 判断是否是json格式请求类型
        if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
            try {
                CustomUser customUser = new ObjectMapper().readValue(request.getInputStream(), CustomUser.class);
                String username = customUser.getUsername();
                String password = customUser.getPassword();
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
                setDetails(request, authenticationToken);
                return this.getAuthenticationManager().authenticate(authenticationToken);
            } catch (IOException e) {
                throw new AuthenticationServiceException(e.getMessage());
            }

        }
        return super.attemptAuthentication(request, response);
    }
}

自定义AuthenticationManager

public class CustomAuthenticationManager implements AuthenticationManager {

    public CustomAuthenticationManager() {
        super();
    }

    private DaoAuthenticationProvider authenticationProvider;

    public void setAuthenticationProvider(DaoAuthenticationProvider authenticationProvider) {
        this.authenticationProvider = authenticationProvider;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        return authenticationProvider.authenticate(authentication);
    }
}

自定义Provider

public class CustomAuthenticationProvider extends DaoAuthenticationProvider {

    public CustomAuthenticationProvider() {
        super();
    }

}

自定义UserDetailsService

这里我为了方便仅构造了一个Map,实际应用中应从数据库获取

@Service
public class CustomUserDetailsService implements UserDetailsService {

    protected static final Map<String, CustomUser> map = new HashMap<>();

    public static Map<String, CustomUser> getMap() {
        return map;
    }

    static {
        String pwd = new BCryptPasswordEncoder().encode("userPwd");
        map.put("user", new CustomUser(1L, "user", pwd));
        map.put("admin", new CustomUser(2L, "admin", pwd));
    }


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        CustomUser customUser = map.get(username);
        if (customUser == null) {
            throw new UsernameNotFoundException("username " + username + " is not found");
        }
        return new CustomUserDetails(customUser);
    }


}

自定义用户信息

public class CustomUser implements Serializable {

    private Long userId;

    private String username;

    private String password;

    public CustomUser() {
    }


    public CustomUser(Long userId, String username, String password) {
        this.userId = userId;
        this.username = username;
        this.password = password;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

自定义认证成功handler

public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        UserDetails customUser = (UserDetails) authentication.getPrincipal();
        String username = customUser.getUsername();
        response.addCookie(new Cookie("username", username));
        WebResponse<String> success = WebResponse.success(username);
        response.getWriter().write(JSON.toJSONString(success));
    }
}

自定义认证失败Handler

package com.study.security.config;

import com.alibaba.fastjson.JSON;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

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

/**
 * @author Say Hello
 * @version 1.0.0
 * @Date 2023/7/27
 * @Description
 */
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        WebResponse<Object> fail = WebResponse.fail();
        response.getWriter().write(JSON.toJSONString(fail));
    }
}

配置SecurityConfig

package com.study.security.config;


import com.study.security.config.cookie.CustomCookieAuthenticationFilter;
import com.study.security.config.phone.CustomPhoneAuthenticationManager;
import com.study.security.config.phone.CustomPhoneAuthenticationProcessingFilter;
import com.study.security.config.phone.CustomPhoneAuthenticationProvider;
import com.study.security.service.CustomPhoneDetailsService;
import com.study.security.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;

/**
 * @author Say Hello
 * @version 1.0.0
 * @Date 2023/7/26
 * @Description
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Value("${spring.security.custom.url.login}")
    String loginUrl;

    @Resource
    CustomUserDetailsService customUserDetailsService;

    @Bean
    public PasswordEncoder customPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Order(1)
    public SecurityFilterChain customSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
                //向SecurityFilterChain中插入自定以的AuthenticationManager
                .authenticationManager(customAuthenticationManager())
                .authenticationManager(customPhoneAuthenticationManager())
                //向SecurityFilterChain中插入自定以的AuthenticationProvider
                .authenticationProvider(customAuthenticationProvider())
                .authenticationProvider(customPhoneAuthenticationProvider())
                .exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                .and()
                .logout().logoutUrl(logoutUrl)
                .logoutSuccessHandler(new CustomLogoutSuccessHandler());
        // 将loginFilter过滤器添加到UsernamePasswordAuthenticationFilter过滤器所在的位置
        http.addFilterAt(customUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter() {
        CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter = new CustomUsernamePasswordAuthenticationFilter();
       //配置登陆认证的入口
        customUsernamePasswordAuthenticationFilter.setFilterProcessesUrl(loginUrl);
        //配置manager
        customUsernamePasswordAuthenticationFilter.setAuthenticationManager(customAuthenticationManager());
        //设置认证成功Handler
        customUsernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(new CustomLoginSuccessHandler());
        //设置认证失败Handler
        customUsernamePasswordAuthenticationFilter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler());
        return customUsernamePasswordAuthenticationFilter;
    }

    @Bean
    public AuthenticationManager customAuthenticationManager() {
        CustomAuthenticationManager customAuthenticationManager = new CustomAuthenticationManager();
        customAuthenticationManager.setAuthenticationProvider(customAuthenticationProvider());
        return customAuthenticationManager;
    }


    @Bean
    public DaoAuthenticationProvider customAuthenticationProvider() {
        CustomAuthenticationProvider customAuthenticationProvider = new CustomAuthenticationProvider();
        customAuthenticationProvider.setPasswordEncoder(customPasswordEncoder());
        customAuthenticationProvider.setUserDetailsService(customUserDetailsService);
        return customAuthenticationProvider;
    }

}

项目验证

未登陆访问

认证失败 

认证成功

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值