003-SpringBoot Security 安全验证——开发使用——登陆与token验证

续上一篇《002-SpringBoot Security 安全验证——开发使用——替换用户

面向前后端分离的模式,通常需要有登陆,然后把Token生成好,返回给客户端,客户端存起来,然后每次请求的时候,都带一个token到服务端,服务端验证通过后放行。

首先开发登陆

package org.tzl.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.tzl.core.ResponseResult;
import org.tzl.domain.po.User;
import org.tzl.service.AuthenticationService;

import javax.annotation.Resource;

/*******************************************************
 * 认证控制器 
 *
 * @author: tzl
 * @time: 2022-05-02 15:58:45
 * *****************************************************/
@RestController
@RequestMapping("authentication")
public class AuthenticationController {

    @Resource
    private AuthenticationService authenticationService;

    @PostMapping("/login")
    public ResponseResult login(@RequestBody User user) {
        return ResponseResult.ok(authenticationService.login(user));
    }

    @GetMapping("/logout/{token}")
    public ResponseResult logout(@PathVariable String token) {
        return ResponseResult.ok(authenticationService.logout(token));
    }
}
package org.tzl.service;

import org.tzl.domain.po.User;

/*******************************************************
 * 认证服务接口 
 *
 * @author: tzl
 * @time: 2022-05-02 16:18:17
 * *****************************************************/
public interface AuthenticationService {
    String login(User user);

    Boolean logout(String token);
}
package org.tzl.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Service;
import org.tzl.core.Cache;
import org.tzl.domain.po.User;
import org.tzl.service.AuthenticationService;
import org.tzl.utils.SnowFlakeIdWorker;

import javax.annotation.Resource;

/*******************************************************
 * 认证服务实现类 
 *
 * @author: tzl
 * @time: 2022-05-02 16:23:36
 * *****************************************************/
@Service
public class AuthenticationServiceImpl implements AuthenticationService {

    @Resource(name = "authenticationManager")
    private AuthenticationManager authenticationManager;
    @Resource(name = "defaultCache")
    private Cache cache;

    @Override
    public String login(User user) {
        // 创建认证用户对象
        Authentication authentication = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
        // 进行认证
        Authentication authenticate = authenticationManager.authenticate(authentication);
        if (authenticate == null || !authenticate.isAuthenticated()) {
            throw new RuntimeException("认证失败");
        }
        // 生成token,没有采用jwt的方式
        String tokenId = String.valueOf(SnowFlakeIdWorker.getId());
        cache.setCache(tokenId, authenticate);
        return tokenId;
    }

    @Override
    public Boolean logout(String token) {
        cache.removeCache(token);
        return true;
    }
}

在认证类中有个关键点,是获取AuthenticationManager 的bean对象,这个需要有SecurityConfig中,进行声明,同时,在SecurityConfig中,要配置对登陆的放行。

package org.tzl.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.tzl.filter.TokenFilter;

import javax.annotation.Resource;

/*******************************************************
 * 安全验证配置 
 *
 * @author: tzl
 * @time: 2022-05-01 11:08:41
 * *****************************************************/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private TokenFilter tokenFilter;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                return rawPassword.toString();
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return rawPassword.toString().equals(encodedPassword);
            }
        };
    }

    // 这个方法,不查资料,找起来得多费事……
    @Bean("authenticationManager")
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        super.configure(http);
        http
                // 关闭跨站请求伪造验证,如果不关闭的话,请求会返回403禁止访问
                .csrf().disable()
                // 取消使用session机制存储登陆状态
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // 对登陆接口要求匿名访问
                .authorizeRequests().antMatchers("/authentication/login").permitAll()
                .anyRequest().authenticated()
                .and()
                // 添加token验证过滤器
                .addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

然后是开发Token过滤器类,对token进行检验

package org.tzl.filter;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.tzl.core.Cache;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/*******************************************************
 * Token验证过滤器 
 *
 * @author: tzl
 * @time: 2022-05-02 17:55:13
 * *****************************************************/
@Component
public class TokenFilter extends OncePerRequestFilter {

    @Resource(name = "defaultCache")
    private Cache cache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String requestURI = request.getRequestURI();

        // 把登陆接口跳过
        if (requestURI.contains("authentication/login")) {
            // 直接跳过
            filterChain.doFilter(request, response);
            return;
        }

        // 取出token值
        String token = requestURI.substring(requestURI.lastIndexOf('/') + 1);

        // 查找认证用户
        Authentication authentication = this.cache.getCache(token);
        if (authentication == null) {
            // 不做认证处理,后面检测认证时,会直接返回未认证
            filterChain.doFilter(request, response);
            return;
        }

        // 进行认证放行
        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
    }
}

注意,中间中断的时候,需要return;不然还会继续向后走代码;

最后附上缓存的代码,没有用Redis,没有缓存时限。只是为了实现验证的功能

package org.tzl.core;

/*******************************************************
 * 缓存接口 
 *
 * @author: tzl
 * @time: 2022-05-02 16:54:11
 * *****************************************************/
public interface Cache {
    <T> void setCache(String key, T data);

    <T> T getCache(String key);

    void removeCache(String key);
}
package org.tzl.core.impl;

import org.springframework.stereotype.Component;
import org.tzl.core.Cache;

import java.util.HashMap;
import java.util.Map;

/*******************************************************
 * 默认缓存类 
 *
 * @author: tzl
 * @time: 2022-05-02 16:56:15
 * *****************************************************/
@Component("defaultCache")
public class DefaultCache implements Cache {

    private volatile Map<String, Object> cache = new HashMap<>();

    @Override
    synchronized public <T> void setCache(String key, T data) {
        cache.put(key, data);
    }

    @Override
    public <T> T getCache(String key) {
        return (T) cache.get(key);
    }

    @Override
    synchronized public void removeCache(String key) {
        if (cache.containsKey(key)) {
            cache.remove(key);
        }
    }
}

写的比较潦草,纯当笔记了

posted on 2022-05-02 19:42  走调的钢琴  阅读(487)  评论(0)    收藏  举报