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); } } }
写的比较潦草,纯当笔记了
浙公网安备 33010602011771号