Verificação de login de separação de front e back-end do SpringBoot Security

Verificação de login de separação de front e back-end do SpringBoot Security

Este artigo é reproduzido. Muito bem escrito, fácil de entender

 

Recentemente, tenho pesquisado o uso do OAuth2, pensando em todo o single sign-on, encontrei muitas demos na Internet, mas não consegui implementá-lo, talvez porque não entenda o princípio do OAuth2. Por alguns dias fiquei cada vez mais sem noção e não conseguia desistir, me virei e pensei, OAuth2 é uma extensão baseada em Segurança, e não sei nada sobre Segurança, então vamos estudar Segurança em primeiro lugar. Aumente a segurança e descubra como ela se sente.

Basta fazer isso e usar o Security no ambiente SpringBoot 2.1.4.RELEASE pronto.
Sem mencionar o uso de segurança simples. O projeto atual é separado dos front-ends e back-ends. O formato dos dados retornados após login bem-sucedido ou falha deve estar no formato JSON. Ele também precisa retornar uma mensagem de prompt no formato JSON quando não estiver logado Ele também precisa retornar JSON ao efetuar logout dos dados formatados. Independentemente da autorização, primeiro retorne os dados no formato JSON. Isso está feito. Também estudei por vários dias e li muita experiência de outras pessoas. Pesquise.

Abaixo, o código:

A primeira etapa é apresentar o arquivo de configuração de segurança em pom.xml

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

A segunda etapa é aumentar o arquivo de configuração de configuração

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

import javax.servlet.http.HttpServletResponse;

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.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * 参考网址:
 * https://blog.csdn.net/XlxfyzsFdblj/article/details/82083443
 * https://blog.csdn.net/lizc_lizc/article/details/84059004
 * https://blog.csdn.net/XlxfyzsFdblj/article/details/82084183
 * https://blog.csdn.net/weixin_36451151/article/details/83868891
 * 查找了很多文件,有用的还有有的,感谢他们的辛勤付出
 * Security配置文件,项目启动时就加载了
 * @author 程就人生
 *
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {      
    
    @Autowired
    private MyPasswordEncoder myPasswordEncoder;
    
    @Autowired
    private UserDetailsService myCustomUserService;
    
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
        http
        .authenticationProvider(authenticationProvider())
        .httpBasic()
        //未登录时,进行json格式的提示,很喜欢这种写法,不用单独写一个又一个的类
            .authenticationEntryPoint((request,response,authException) -> {
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                PrintWriter out = response.getWriter();
                Map<String,Object> map = new HashMap<String,Object>();
                map.put("code",403);
                map.put("message","未登录");
                out.write(objectMapper.writeValueAsString(map));
                out.flush();
                out.close();
            })
            
            .and()
            .authorizeRequests()
            .anyRequest().authenticated() //必须授权才能范围
            
            .and()
            .formLogin() //使用自带的登录
            .permitAll()
            //登录失败,返回json
            .failureHandler((request,response,ex) -> {
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                PrintWriter out = response.getWriter();
                Map<String,Object> map = new HashMap<String,Object>();
                map.put("code",401);
                if (ex instanceof UsernameNotFoundException || ex instanceof BadCredentialsException) {
                    map.put("message","用户名或密码错误");
                } else if (ex instanceof DisabledException) {
                    map.put("message","账户被禁用");
                } else {
                    map.put("message","登录失败!");
                }
                out.write(objectMapper.writeValueAsString(map));
                out.flush();
                out.close();
            })
            //登录成功,返回json
            .successHandler((request,response,authentication) -> {
                Map<String,Object> map = new HashMap<String,Object>();
                map.put("code",200);
                map.put("message","登录成功");
                map.put("data",authentication);
                response.setContentType("application/json;charset=utf-8");
                PrintWriter out = response.getWriter();
                out.write(objectMapper.writeValueAsString(map));
                out.flush();
                out.close();
            })
            .and()
            .exceptionHandling()
            //没有权限,返回json
            .accessDeniedHandler((request,response,ex) -> {
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                PrintWriter out = response.getWriter();
                Map<String,Object> map = new HashMap<String,Object>();
                map.put("code",403);
                map.put("message", "权限不足");
                out.write(objectMapper.writeValueAsString(map));
                out.flush();
                out.close();
            })
            .and()
            .logout()
            //退出成功,返回json
            .logoutSuccessHandler((request,response,authentication) -> {
                Map<String,Object> map = new HashMap<String,Object>();
                map.put("code",200);
                map.put("message","退出成功");
                map.put("data",authentication);
                response.setContentType("application/json;charset=utf-8");
                PrintWriter out = response.getWriter();
                out.write(objectMapper.writeValueAsString(map));
                out.flush();
                out.close();
            })
            .permitAll();
            //开启跨域访问
            http.cors().disable();
            //开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
            http.csrf().disable();
    }
    
    @Override
    public void configure(WebSecurity web) {
        //对于在header里面增加token等类似情况,放行所有OPTIONS请求。
        web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        //对默认的UserDetailsService进行覆盖
        authenticationProvider.setUserDetailsService(myCustomUserService);
        authenticationProvider.setPasswordEncoder(myPasswordEncoder);
        return authenticationProvider;
    }
    
}

A terceira etapa é implementar a interface UserDetailsService

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.Component;
/**
 * 登录专用类
 * 自定义类,实现了UserDetailsService接口,用户登录时调用的第一类
 * @author 程就人生
 *
 */
@Component
public class MyCustomUserService implements UserDetailsService {

    /**
     * 登陆验证时,通过username获取用户的所有权限信息
     * 并返回UserDetails放到spring的全局缓存SecurityContextHolder中,以供授权器使用
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //在这里可以自己调用数据库,对username进行查询,看看在数据库中是否存在
        MyUserDetails myUserDetail = new MyUserDetails();
        myUserDetail.setUsername(username);
        myUserDetail.setPassword("123456");
        return myUserDetail;
    }
}

Descrição: Esta classe é utilizada principalmente para receber o nome de usuário passado pelo login, podendo então ser estendida aqui para consultar se o nome de usuário existe no banco de dados. Caso não exista, uma exceção pode ser lançada. Para fins de demonstração, este teste grava os dados até a morte.

A quarta etapa é implementar a interface PasswordEncoder

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
 * 自定义的密码加密方法,实现了PasswordEncoder接口
 * @author 程就人生
 *
 */
@Component
public class MyPasswordEncoder implements PasswordEncoder {

    @Override
    public String encode(CharSequence charSequence) {
        //加密方法可以根据自己的需要修改
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return encode(charSequence).equals(s);
    }
}

Descrição: Esta classe é usada principalmente para processar criptografia de senha e comparar a senha passada pelo usuário com a senha do banco de dados (a senha em UserDetailsService).

A quinta etapa é implementar a interface UserDetails

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

/**
 * 实现了UserDetails接口,只留必需的属性,也可添加自己需要的属性
 * @author 程就人生
 *
 */
@Component
public class MyUserDetails implements UserDetails {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    //登录用户名
    private String username;
    //登录密码
    private String password;

    private Collection<? extends GrantedAuthority> authorities;

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

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

    public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

Descrição: Esta classe é usada para armazenar dados do usuário após o login bem-sucedido. Após o login bem-sucedido, você pode usar o seguinte código para obter:

MyUserDetails myUserDetails= (MyUserDetails) SecurityContextHolder.getContext().getAuthentication() .getPrincipal();

Depois que o código for escrito, você precisará testá-lo. Somente após o teste você poderá provar a validade do código. Vamos usar um navegador primeiro.

A primeira etapa do teste é visitar o índice antes de fazer login, e a página é redirecionada diretamente para a página de login padrão e a interface de teste está OK.

figura 1

Na segunda etapa do teste, após o login, os dados json são retornados e o resultado do teste está OK.

Figura 2

A terceira etapa é testar, acessar o índice, retornar os dados de login de saída e o resultado do teste está OK.

imagem 3

A quarta etapa é acessar o logout, retornar os dados json e testar a interface OK.

Figura 4

O quinto passo é testar com a API POST, utilizar esta ferramenta para simular a requisição ajax, e ver o resultado da requisição. Primeiro, visite o índice, que só pode ser acessado após o login. O resultado do teste está ok e os dados no formato JSON que precisamos são retornados.

Figura 5

A sexta etapa, na caixa de diálogo de simulação de login, defina a variável de ambiente para manter o status de login.

Figura 6

**Etapa 7, teste de login, retorna dados no formato JSON e o resultado do teste está OK.

 

Figura 7

A oitava etapa é retornar à janela de teste do índice, enviar uma solicitação e retornar as informações do usuário atual no formato JSON, e o resultado do teste está OK.

Figura 8

Etapa 9: saia do teste, retorne os dados no formato JSON e o resultado do teste está OK

Figura 9

Décimo passo, após sair, visite novamente o índice, há um problema, os dados de login ainda estão lá, OLHE!

Figura 10

 

Remova a marca na frente do cabeçalho, ou seja, remova o cookie. É normal neste momento. O motivo é muito simples. Ao sair, o cookie não é limpo. Isso só pode ser testado em um ambiente formal. Não importa o quão simulado seja o API POST, ele ainda é diferente do ambiente formal.

Se um erro 403 for relatado no teste API POST, você precisará adicionar o arquivo de configuração

//开启跨域访问
http.cors().disable();
//开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
http.csrf().disable();

 

Acho que você gosta

Origin blog.csdn.net/zs319428/article/details/107089473
Recomendado
Clasificación