Seguridad integrada SpringBoot (3) | (inicio de sesión personalizado de seguridad y manejo de excepciones)

Seguridad integrada SpringBoot (3) | (Proceso de respuesta e inicio de sesión de separación de front-end y back-end de Security)


Capítulo
Vínculo del capítulo 1: Seguridad integrada SpringBoot (1) | (Entrada de seguridad)
Vínculo del capítulo 2: Seguridad integrada SpringBoot (2) | (configuración personalizada de seguridad)
Vínculo del capítulo 3: Seguridad integrada SpringBoot (3) | (seguridad Front-end y back -fin de separación de inicio de sesión y procesamiento de respuesta)
Enlace del capítulo 4: seguridad integrada SpringBoot (4) | (La seguridad se basa en JWT para lograr la separación de front-end y el inicio de sesión personalizado)

prefacio

En el capítulo anterior, presentamos la configuración de usuarios basada en seguridad, la configuración de permisos y la configuración de recursos de Springboot. Pero en la práctica, se encuentra que la autenticación utiliza el formulario de inicio de sesión predeterminado para iniciar sesión, y el informe de errores de las excepciones de autenticación y autorización es relativamente tosco y devuelve directamente una cadena de JSON, que es muy poco amigable, ya que la mayoría de los proyectos ahora están separados. desde el front-end y el back-end, lo discutiremos en este capítulo Realice el inicio de sesión basado en la separación del front-end y el back-end, y la administración rápida

Este artículo es una ampliación sobre la base del anterior, si no tienes muy clara la base del proyecto, por favor revisa el artículo anterior

1. Dependencias del proyecto

Incluye principalmente dependencias de seguridad y algunas dependencias de herramientas.

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.7</version>
        <relativePath/>
    </parent>
   <dependencies>
       <!--    springboot   start-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <!--    springboot依赖    end-->

       <!--wagger2依赖start-->
       <dependency>
           <groupId>com.github.xiaoymin</groupId>
           <artifactId>knife4j-spring-ui</artifactId>
           <version>3.0.3</version>
       </dependency>
       <dependency>
           <groupId>io.springfox</groupId>
           <artifactId>springfox-swagger2</artifactId>
           <version>3.0.0</version>
       </dependency>

       <!--常用工具依赖start-->
       <dependency>
           <groupId>org.apache.commons</groupId>
           <artifactId>commons-lang3</artifactId>
           <version>3.12.0</version>
       </dependency>

       <dependency>
           <groupId>org.apache.commons</groupId>
           <artifactId>commons-collections4</artifactId>
           <version>4.1</version>
       </dependency>

       <dependency>
           <groupId>com.google.guava</groupId>
           <artifactId>guava</artifactId>
           <version>30.1.1-jre</version>
       </dependency>

       <!--fastjson引入-->
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>fastjson</artifactId>
           <version>1.2.49</version>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>commons-codec</groupId>
           <artifactId>commons-codec</artifactId>
           <version>1.15</version>
       </dependency>

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

       <!--数据库引入引入-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-data-jpa</artifactId>
       </dependency>
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <scope>runtime</scope>
       </dependency>
   </dependencies>

2. Procesamiento de respuestas personalizadas

El procesamiento de respuesta personalizado es principalmente para definir el formato de respuesta, lo cual es conveniente para la coordinación de front-end y back-end.

1. Defina el cuerpo de respuesta ResponseHandle

@Data
public class ResponseHandle<T> {
    
    
    private String status;
    private String desc;
    private T data;

    // 成功 无参构成函数
    public static ResponseHandle SUCCESS(){
    
    
        ResponseHandle result = new ResponseHandle();
        result.setDesc("成功");
        result.setResultCode(HttpCode.SUCCESS);
        return result;
    }
    //成功 有返回数据构造函数
    public static ResponseHandle SUCCESS(Object data){
    
    
        ResponseHandle result = new ResponseHandle();
        result.setData(data);
        result.setResultCode(HttpCode.SUCCESS);
        return result;
    }

    /**
     * 失败,指定status、desc
     */
    public static ResponseHandle FAIL(String status, String desc) {
    
    
        ResponseHandle result = new ResponseHandle();
        result.setStatus(status);
        result.setDesc(desc);
        return result;
    }

    /**
     * 失败,指定ResultCode枚举
     */
    public static ResponseHandle FAIL(HttpCode resultCode) {
    
    
        ResponseHandle result = new ResponseHandle();
        result.setResultCode(resultCode);
        return result;
    }
    /**
     * 把ResultCode枚举转换为ResResult
     */
    private void setResultCode(HttpCode code) {
    
    
        this.status = code.code();
        this.desc = code.message();
    }
}

2. Defina la enumeración de respuesta HttpCode

public enum HttpCode {
    
    

    // 成功状态码
    SUCCESS("00000", "成功"),
    UNKNOWN_ERROR("99999", "服务未知异常"),
    // 系统500错误
    SYSTEM_ERROR("10000", "系统异常,请稍后重试"),


    // 认证错误:20001-29999
    USER_NOAUTH("20000", "用户未登录"),
    TOKEN_ERROR("20001", "生成token失败"),
    LOGIN_ERROR("20002", "登录失败"),
    USER_LOCKED("20004", "账户已锁定"),
    USER_PASS_OUT("20005", "用户名或密码错误次数过多"),
    USER_NOTFIND_ERROR("20006", "没有找到用户"),
    USER_ERROR("20007", "用户名或密码不正确"),

    ;


    private String code;

    private String message;

    HttpCode(String code, String message) {
    
    
        this.code = code;
        this.message = message;
    }

    public String code() {
    
    
        return this.code;
    }

    public String message() {
    
    
        return this.message;
    }
}

3. Clase de respuesta de inicio de sesión personalizada

Principalmente, el formato de la información regresa al front-end en escenarios como inicio de sesión exitoso, inicio de sesión fallido, sin inicio de sesión y cierre de sesión

1. Procesamiento de errores de inicio de sesión

@Component
public class LoginFailureHandler 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);
        ResponseHandle fail = ResponseHandle.FAIL(HttpCode.USER_ERROR);
        response.getWriter().write(JSONObject.toJSONString(fail));
    }
}

2. Procesamiento de inicio de sesión exitoso

public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    
    


    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
    
    
    }

    @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);
        ResponseHandle success = ResponseHandle.SUCCESS(authentication);
        response.getWriter().write(JSONObject.toJSONString(success));
    }
}

3. Procesamiento de cierre de sesión exitoso

public class LogoutMySuccessHandler implements LogoutSuccessHandler {
    
    

    @Override
    public void onLogoutSuccess(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);
        ResponseHandle success = ResponseHandle.SUCCESS("登出成功");
        response.getWriter().write(JSONObject.toJSONString(success));
    }
}

4. Sin procesamiento de inicio de sesión

public class NoLoginHandler implements AuthenticationEntryPoint {
    
    

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    
    
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        ResponseHandle fail = ResponseHandle.FAIL(HttpCode.USER_NOAUTH);
        response.getWriter().write(JSONObject.toJSONString(fail));
    }
}

Cuarto, implementación de inicio de sesión personalizado

1. Introducción a UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter es la clase de implementación de seguridad que implementa el inicio de sesión con nombre de usuario y contraseña de forma predeterminada. Cuando usamos el formulario de inicio de sesión predeterminado y otras rutas, ingresaremos esta clase para el procesamiento de inicio de sesión del usuario.Si queremos personalizar la interfaz de inicio de sesión, entonces tenemos que reescribir esta clase.

2. Implementación de inicio de sesión con nombre de usuario y contraseña personalizados

Aquí, los usuarios pueden personalizar su propia lógica de implementación, como el cifrado de la contraseña del usuario, los parámetros de retorno, etc.

@Slf4j
public class MyUsernamePasswordAuthenticationFilter 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());
        }
        if(request.getContentType().equals("application/json")){
    
    
            try {
    
    
                //参数转换为map
                Map map = new ObjectMapper().readValue(request.getInputStream(),Map.class);
                String username = (String) map.get("username");
                String password = (String) map.get("password");
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
        return super.attemptAuthentication(request, response);
    }
}

3. Configuración de seguridad

Configure las fuentes de seguridad de los usuarios, los modos de autenticación, los filtros, etc. Aquí definimos nuestra propia interfaz de inicio de sesión /mylogin

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    @Autowired
    private UserServiceImpl userService;


    /**
     * 常用的三种存储方式,项目找那个用的最多的为,自定义用户存储
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        //1、内存用户配置
//        auth.inMemoryAuthentication().passwordEncoder(bCryptPasswordEncoder())
//                .withUser("admin").password(bCryptPasswordEncoder().encode("123456")).authorities("ADMIN")
//                .and()
//                .withUser("test").password(bCryptPasswordEncoder().encode("123456")).authorities("TEST");
        //2、数据库用户配置
//        auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder())
//                .usersByUsernameQuery(
//                        "select username, password, status from Users where username = ?")
//                .authoritiesByUsernameQuery(
//                        "select username, authority from Authority where username = ?");
        //3、自定义用户存储
        auth.userDetailsService(userService)
                .passwordEncoder(bCryptPasswordEncoder());
    }

    /**
     * configure(WebSecurity)用于影响全局安全性(配置资源,设置调试模式,通过实现自定义防火墙定义拒绝请求)的配置设置。
     * 一般用于配置全局的某些通用事物,例如静态资源等
     *
     * @param web
     */
    @Override
    public void configure(WebSecurity web) {
    
    
        web.ignoring()
                .antMatchers(HttpMethod.OPTIONS, "/**")  ///跨域请求预处理
                .antMatchers("/favicon.ico")
                .antMatchers("/swagger**")   // 以下swagger静态资源、接口不拦截
                .antMatchers("/doc.html")
                .antMatchers("/swagger-resources/**")
                .antMatchers("/v2/api-docs")
                .antMatchers("/webjars/**")
                .antMatchers("/test/**")
                .antMatchers("/js/**", "/css/**", "/images/**");  // 排除html静态资源
    }

    /**
     * 配置接口拦截
     * configure(HttpSecurity)允许基于选择匹配在资源级配置基于网络的安全性,
     * 也就是对角色所能访问的接口做出限制
     *
     * @param httpSecurity 请求属性
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
    
    
        httpSecurity.authorizeRequests()
                .antMatchers(HttpMethod.GET, "/demo/get").permitAll()
                //指定权限为ROLE_ADMIN才能访问,这里和方法注解配置效果一样,但是会覆盖注解
                .antMatchers("/demo/delete").hasRole("ADMIN")
                // 所有请求都需要验证
                .anyRequest().authenticated()
                .and()
                //.httpBasic() Basic认证,和表单认证只能选一个
                // 使用表单认证页面
                .formLogin()
                .and().addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling()
                .authenticationEntryPoint(new NoLoginHandler())
                .and()
                .logout()
                .logoutSuccessHandler(new LogoutMySuccessHandler())
                .and()
                .csrf().disable();
    }


    /**
     * 配置用户认证方式
     *
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
    
    
        return super.authenticationManagerBean();
    }


    /**
     * 自定义过滤器,用来替换security的默认过滤器(UsernamePasswordAuthenticationFilter),
     * 实现自定义的login接口,接口路径为了区别默认的/login我们定义为/mylogin
     *
     * @return
     * @throws Exception
     */
    @Bean
    public MyUsernamePasswordAuthenticationFilter loginFilter() throws Exception {
    
    
        MyUsernamePasswordAuthenticationFilter loginFilter = new MyUsernamePasswordAuthenticationFilter();
        loginFilter.setFilterProcessesUrl("/mylogin");
        loginFilter.setAuthenticationSuccessHandler(new LoginSuccessHandler());
        loginFilter.setAuthenticationFailureHandler(new LoginFailureHandler());
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        return loginFilter;
    }

    /**
     * 使用security 提供的加密规则(还有其他加密方式)
     *
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
    
    
        return new BCryptPasswordEncoder();
    }

}

4. Verificar inicio de sesión

1. Escriba la interfaz de prueba

@Api(tags = {
    
    "演示相关接口"})
@RestController
@RequestMapping("/demo")
public class DemoCtrl {
    
    

    @Autowired
    private UserRepository userRepository;

    @Autowired
        private BCryptPasswordEncoder bCryptPasswordEncoder;


    @ApiOperation(value = "获取接口", notes = "获取接口")
    @GetMapping(value = "/get")
    public ResponseHandle get() {
    
    
        return ResponseHandle.SUCCESS("获取数据成功");
    }
    @ApiOperation(value = "获取接口", notes = "获取接口")
    @GetMapping(value = "/find")
    public ResponseHandle find() {
    
    
        return ResponseHandle.SUCCESS("查询数据成功");
    }

    @ApiOperation(value = "注册用户", notes = "注册")
    @PostMapping("/register")
    public String registerUser(@RequestBody Map<String, String> registerUser) {
    
    
        User user = new User();
        user.setUsername(registerUser.get("username"));
        user.setPassword(bCryptPasswordEncoder.encode(registerUser.get("password")));
        user.setRole("ROLE_USER");
        User save = userRepository.save(user);
        return save.toString();
    }

    @ApiOperation(value = "修改用户", notes = "修改")
    @GetMapping("/update")
//    @PreAuthorize("hasRole('ROLE_ADMIN')")
    public ResponseHandle updateUser() {
    
    
        return ResponseHandle.SUCCESS("数据修改成功");
    }
    @ApiOperation(value = "删除用户", notes = "删除")
    @GetMapping("/delete")
    public ResponseHandle deleteUser() {
    
    
        return ResponseHandle.SUCCESS("数据删除成功");
    }
}

2. Indicador de interfaz de llamada

1. No hay interfaz de llamada de inicio de sesión

inserte la descripción de la imagen aquí

2. Llame a la interfaz de inicio de sesión

inserte la descripción de la imagen aquí

Resumir

En este punto, springboot integra la seguridad para completar la configuración de la base de datos del usuario y el inicio de sesión personalizado, y mejora las respuestas relacionadas con el inicio de sesión, lo que facilita el procesamiento unificado de los extremos frontal y posterior. Este tipo de inicio de sesión puede satisfacer las necesidades de un solo proyecto, pero teniendo en cuenta la autenticación de inicio de sesión entre varios proyectos, todavía hay muchos problemas en esta solución. A continuación, continuaremos completando el método de autenticación basado en token.

Enlaces al Capítulo 1: SpringBoot Integrated Security (1) | (Introducción a la seguridad)
Enlaces al Capítulo 2: SpringBoot Integrated Security (2) | (Configuración personalizada de seguridad)
Enlaces al Capítulo 3: SpringBoot Integrated Security (3) | (Antes y después Seguridad Procesamiento de respuestas e inicio de sesión separados por extremos)
Enlace del capítulo 4: Seguridad integrada de SpringBoot (4) | (La seguridad se basa en JWT para lograr la separación de front-end y el inicio de sesión personalizado)

Supongo que te gusta

Origin blog.csdn.net/Oaklkm/article/details/128144676
Recomendado
Clasificación