Serie SpringBoot: integración de Spring Security

Plataforma de autor:

| CSDN: blog.csdn.net/qq_41153943

| Pepitas: juejin.cn/user/651387…

| Zhihu: www.zhihu.com/people/1024…

| GitHub: github.com/JiangXia-10…

| Cuenta pública de WeChat: 1024 notas

Este artículo tiene aproximadamente 2230 palabras y el tiempo de lectura esperado es de 15 minutos.

1. Introducción a Spring Security

1. Introducción al marco

Spring es un marco de desarrollo de aplicaciones Java muy popular y exitoso. Basado en el marco Spring, Spring Security proporciona una solución completa para la seguridad de aplicaciones web. En términos generales, la seguridad de las aplicaciones Web incluye dos partes: la autenticación del usuario (Autenticación*) y la autorización del usuario (Autorización) .

(1) La autenticación de usuario se refiere a: verificar si un usuario es un sujeto legítimo en el sistema, es decir, si el usuario puede acceder al sistema. La autenticación de usuario generalmente requiere que el usuario proporcione un nombre de usuario y una contraseña. El sistema completa el proceso de autenticación verificando el nombre de usuario y la contraseña.

(2) La autorización del usuario se refiere a verificar si un usuario tiene permiso para realizar una operación. En un sistema, diferentes usuarios tienen diferentes permisos. Por ejemplo, para un archivo, algunos usuarios solo pueden leerlo, mientras que otros pueden modificarlo. En términos generales, el sistema asigna diferentes roles a diferentes usuarios, y cada rol corresponde a una serie de permisos.

Spring Security en realidad usa el filtro para filtrar la ruta de múltiples solicitudes.

(1) Si se basa en la sesión, Spring-security analizará el ID de sesión en la cookie, buscará la información de la sesión almacenada en el servidor y luego juzgará si el usuario actual cumple con los requisitos de la solicitud.

(2) Si es un token, analiza el token y luego agrega la solicitud actual a la información de permisos administrada por Spring-security

2. Ideas de implementación de autenticación y autorización

Si hay muchos módulos en el sistema, cada módulo debe ser autorizado y autenticado, por lo que elegimos la autorización y autenticación basadas en tokens. El usuario se autentica con éxito de acuerdo con el nombre de usuario y la contraseña, y luego obtiene una serie de valores de permiso de el rol de usuario actual, y usa el usuario El nombre es clave, la lista de permisos se almacena en el caché de redis en forma de valor, y el token se genera y se devuelve de acuerdo con la información relacionada con el nombre de usuario.El navegador registra el token en la cookie, y el token se lleva al encabezado de solicitud de encabezado de forma predeterminada cada vez que se llama a la interfaz api. Spring-security analiza el encabezado para obtener información del token, analiza el token para obtener el nombre de usuario actual y obtiene la lista de permisos de redis de acuerdo con el nombre de usuario, para que Spring-security pueda determinar si la solicitud actual tiene permiso para acceder

2. Integrar Spring Security

1. Primero cree módulos relacionados con Spring Security y arquitectura de código

imagen.png

imagen.png

2. Introducir dependencias relacionadas en spring_security

<dependencies>
    <dependency>
        <groupId>com.jiangxia</groupId>
        <artifactId>common_utils</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    <!-- Spring Security依赖 -->

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>
</dependencies>
复制代码

3. Introducir dependencias spring_security en módulos que necesitan usar spring_security

<dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>spring_security</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
复制代码

4. Descripción del código principal

La configuración central de Spring Security es heredar la configuración de WebSecurityConfigurerAdapter y anotar @EnableWebSecurity. Esta configuración especifica el método de procesamiento de nombre de usuario y contraseña, apertura y cierre de rutas de solicitud, control de inicio y cierre de sesión y otras configuraciones relacionadas con la seguridad. El código principal es el siguiente:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
    private UserDetailsService userDetailsService;
    private TokenManager tokenManager;
    private DefaultPasswordEncoder defaultPasswordEncoder;
    private RedisTemplate redisTemplate;
    @Autowired
    public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.userDetailsService = userDetailsService;
        this.defaultPasswordEncoder = defaultPasswordEncoder;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 配置设置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                .authenticationEntryPoint(new UnauthorizedEntryPoint())
                .and().csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and().logout().logoutUrl("/admin/acl/index/logout")
                .addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and().addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate)).addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
    }
    /**
     * 密码处理
     * @param auth
     * @throws Exception
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
    }
    //配置哪些请求不拦截
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/api/**",
                "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"
               );
    }
}
复制代码

Cree clases de herramientas relacionadas con la autenticación y la autorización:

1. Método de procesamiento de contraseñas

@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
    public DefaultPasswordEncoder() {
        this(-1);
    }
    /**
     * @param strength
     *            the log rounds to use, between 4 and 31
     */
    public DefaultPasswordEncoder(int strength) {
    }
    public String encode(CharSequence rawPassword) {
        return MD5.encrypt(rawPassword.toString());
    }
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
    }
}
复制代码

2. Clase de herramienta para operación de token

@Component
public class TokenManager {
    private long tokenExpiration = 24*60*60*1000;
    private String tokenSignKey = "123456";
    public String createToken(String username) {
        String token = Jwts.builder().setSubject(username)
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
        return token;
    }
    public String getUserFromToken(String token) {
        String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
        return user;
    }
    public void removeToken(String token) {
        //jwttoken无需删除,客户端扔掉即可。
    }
}
复制代码

3. Clase de implementación de cierre de sesión e inicio de sesión

/**
 * 
 * 登出业务逻辑类
 *
 */
public class TokenLogoutHandler implements LogoutHandler {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        String token = request.getHeader("token");
        if (token != null) {
            tokenManager.removeToken(token);
            //清空当前用户缓存中的权限数据
            String userName = tokenManager.getUserFromToken(token);
            redisTemplate.delete(userName);
        }
        ResponseUtil.out(response, R.ok());
    }
}
复制代码

4. Método de procesamiento unificado para no autorizado

public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        ResponseUtil.out(response, R.error());
    }
}
复制代码

5. Crea un filtro de autenticación y autorización

/**
 * 
 * 登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验,认证
 * 
 */
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
    private AuthenticationManager authenticationManager;
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.authenticationManager = authenticationManager;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.setPostOnly(false);
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException {
        try {
            User user = new ObjectMapper().readValue(req.getInputStream(), User.class);
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 登录成功
     * @param req
     * @param res
     * @param chain
     * @param auth
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                            Authentication auth) throws IOException, ServletException {
        SecurityUser user = (SecurityUser) auth.getPrincipal();
        String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());
        ResponseUtil.out(res, R.ok().data("token", token));
    }
    /**
     * 登录失败
     * @param request
     * @param response
     * @param e
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException e) throws IOException, ServletException {
        ResponseUtil.out(response, R.error());
    }
}
复制代码
/**
 * 访问过滤器:授权filter
 * 
 */
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) {
        super(authManager);
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }
    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        logger.info("================="+req.getRequestURI());
        if(req.getRequestURI().indexOf("admin") == -1) {
            chain.doFilter(req, res);
            return;
        }
        UsernamePasswordAuthenticationToken authentication = null;
        try {
            authentication = getAuthentication(req);
        } catch (Exception e) {
            ResponseUtil.out(res, R.error());
        }
        if (authentication != null) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } else {
            ResponseUtil.out(res, R.error());
        }
        chain.doFilter(req, res);
    }
    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // token置于header里
        String token = request.getHeader("token");
        if (token != null && !"".equals(token.trim())) {
            String userName = tokenManager.getUserFromToken(token);
            List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            for(String permissionValue : permissionValueList) {
                if(StringUtils.isEmpty(permissionValue)) continue;
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
                authorities.add(authority);
            }
            if (!StringUtils.isEmpty(userName)) {
                return new UsernamePasswordAuthenticationToken(userName, token, authorities);
            }
            return null;
        }
        return null;
    }
}
复制代码

Resumir

Lo anterior se trata de la lógica relevante de la seguridad de primavera en springboot. El proceso de autenticación y autorización de Spring Security es aproximadamente el mismo que el de los interceptores personalizados para la interceptación de permisos. El proceso de autenticación es que el usuario del cliente inicia sesión, luego el servidor almacena en caché la información de inicio de sesión del usuario y, finalmente, el servidor devuelve la información del usuario (información básica, permisos, token, etc.) al cliente. En el proceso de autorización, en primer lugar, el cliente inicia una solicitud con un token y el servidor analiza el token para determinar si el usuario ha iniciado sesión, luego consulta el menú del usuario desde el caché, determina si el usuario tiene permiso para solicitar el menú, y finalmente devuelve los datos al cliente.

sugerencia relacionada

Supongo que te gusta

Origin blog.csdn.net/qq_41153943/article/details/125118857
Recomendado
Clasificación