• (微服务多模块)Springboot+Security+Redis+JWT 仅需一招


    小编在开发阶段发现,在现在众多文章中的教程里,虽然有许许多多的Springboot+Security+Redis+JWT,但是那些教程基本的运行环境都在单个项目单个模块中进行开发和测试的,这使得小编在实际的开发过程中,不能Ctrl+C and Ctrl+V直接完全解决这个登录认证的事情。故有这篇文章。

     

    目录

     

    1.项目结构

    2.Common模块

    pom.xml

    2.1 RedisConfig

    2.2 RedisUtil

    2.3 ResponseUtil

    2.4  TokenUtil

    3.model模块

    3.1 User

    4.service模块

    pom.xml

    4.1 UserMapper

    4.2 重点说明

    4.3 MyUserDetailService

    5.spring_security模块

    5.1 DiyUserDetails(UserDetails)

    5.2 WebSecurityConfig(WebSecurityConfigurerAdapter)

    5.3 TokenAuthenticationFilter(BasicAuthenticationFilter)

    5.4 TokenLoginFilter(UsernamePasswordAuthenticationFilter)

    5.5 TokenOncePerRequestFilter(OncePerRequestFilter)

    5.6 LoginAuthenticationEntryPoint(AuthenticationEntryPoint)

    5.7 LoginInFailHandler(AuthenticationFailureHandler)

    5.8 LoginInSuccessHandler(AuthenticationSuccessHandler)

    5.9 LogOutSuccessHandler(LogoutSuccessHandler)

    5.10 NothingAccessDeniedHandler(AccessDeniedHandler)

    6.测试


    1.项目结构

    涉及的模块有common(Redis配置文件、Redis工具、Token工具、返回信息的工具;即如下文件RedisConfig、RedisUtil、TokenUtil、ResponseUtil)、model(user的实体类)、service(主要利用MySQL查询用户数据)、spring_security(多个配置文件,配置security的)。

    下面小编将全部一一介绍并且源码展示出来。

    2.Common模块

    pom.xml

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-securityartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>org.springframework.bootgroupId>
    7. <artifactId>spring-boot-starter-data-redisartifactId>
    8. dependency>
    9. <dependency>
    10. <groupId>io.jsonwebtokengroupId>
    11. <artifactId>jjwtartifactId>
    12. dependency>

    2.1 RedisConfig

    1. import com.fasterxml.jackson.annotation.JsonAutoDetect;
    2. import com.fasterxml.jackson.annotation.PropertyAccessor;
    3. import com.fasterxml.jackson.databind.ObjectMapper;
    4. import org.springframework.cache.annotation.CachingConfigurerSupport;
    5. import org.springframework.cache.annotation.EnableCaching;
    6. import org.springframework.context.annotation.Bean;
    7. import org.springframework.context.annotation.Configuration;
    8. import org.springframework.data.redis.connection.RedisConnectionFactory;
    9. import org.springframework.data.redis.core.RedisTemplate;
    10. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    11. import org.springframework.data.redis.serializer.StringRedisSerializer;
    12. import javax.annotation.Resource;
    13. /*
    14. * Redis配置
    15. * 解决redis在业务逻辑处理层上不出错,缓存序列化问题
    16. * */
    17. @Configuration
    18. @EnableCaching
    19. public class RedisConfig extends CachingConfigurerSupport {
    20. @Resource
    21. RedisConnectionFactory redisConnectionFactory;
    22. @Bean
    23. public RedisTemplate redisTemplate(){
    24. RedisTemplate redisTemplate=new RedisTemplate<>();
    25. redisTemplate.setConnectionFactory(redisConnectionFactory);
    26. //Json序列化配置
    27. //1、String的序列化
    28. StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
    29. // key采用String的序列化方式
    30. redisTemplate.setKeySerializer(stringRedisSerializer);
    31. // hash的key也采用String的序列化方式
    32. redisTemplate.setHashKeySerializer(stringRedisSerializer);
    33. //2、json解析任意的对象(Object),变成json序列化
    34. Jackson2JsonRedisSerializer serializer=new Jackson2JsonRedisSerializer(Object.class);
    35. ObjectMapper mapper=new ObjectMapper(); //用ObjectMapper进行转义
    36. mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    37. //该方法是指定序列化输入的类型,就是将数据库里的数据按照一定类型存储到redis缓存中。
    38. mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    39. serializer.setObjectMapper(mapper);
    40. // value序列化方式采用jackson
    41. redisTemplate.setValueSerializer(serializer);
    42. // hash的value序列化方式采用jackson
    43. redisTemplate.setHashValueSerializer(serializer);
    44. return redisTemplate;
    45. }
    46. }
    47. 2.2 RedisUtil

      1. import org.springframework.beans.factory.annotation.Autowired;
      2. import org.springframework.data.redis.core.StringRedisTemplate;
      3. import org.springframework.stereotype.Component;
      4. import javax.annotation.PostConstruct;
      5. import java.time.LocalDateTime;
      6. import java.time.format.DateTimeFormatter;
      7. import java.time.temporal.ChronoUnit;
      8. import java.util.concurrent.TimeUnit;
      9. @Component
      10. public class RedisUtil {
      11. @Autowired
      12. private StringRedisTemplate stringRedisTemplate;
      13. public static StringRedisTemplate stringRedisTemplateStatic;
      14. @PostConstruct //在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
      15. public void initStringRedisTemplate(){
      16. stringRedisTemplateStatic=this.stringRedisTemplate;
      17. }
      18. private static final DateTimeFormatter df=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
      19. /*
      20. * 保存token信息到redis,也可直接在创建token中使用该方法
      21. * */
      22. public static void redis_SaveTokenInfo(String token,String username){
      23. //以username做key
      24. LocalDateTime localDateTime=LocalDateTime.now();
      25. stringRedisTemplateStatic.opsForHash().put(username,"token",token);
      26. stringRedisTemplateStatic.opsForHash().put(username,"refreshTime", //有效时间
      27. df.format(localDateTime.plus(7*24*60*60*1000, ChronoUnit.MILLIS)));
      28. stringRedisTemplateStatic.opsForHash().put(username,"expiration", //过期时间 5分钟 300秒
      29. df.format(localDateTime.plus(300*1000, ChronoUnit.MILLIS)));
      30. stringRedisTemplateStatic.expire(username,7*24*60*60*1000, TimeUnit.SECONDS);
      31. }
      32. /*
      33. * 检查redis是否存在token
      34. * */
      35. public static boolean hasToken(String username){
      36. return stringRedisTemplateStatic.opsForHash().getOperations().hasKey(username);
      37. }
      38. }

      2.3 ResponseUtil

      1. import com.fasterxml.jackson.databind.ObjectMapper;
      2. import lombok.Data;
      3. import org.apache.ibatis.annotations.Result;
      4. import org.springframework.http.HttpStatus;
      5. import org.springframework.http.MediaType;
      6. import javax.servlet.http.HttpServletResponse;
      7. import java.io.IOException;
      8. import java.io.PrintWriter;
      9. import java.util.HashMap;
      10. import java.util.Map;
      11. @Data
      12. public class ResponseUtil {
      13. public static int OK = 200;
      14. public static int ERROR = 404;
      15. public static String SUCCESS="操作成功!";
      16. public static String NO_SUCCESS="操作失败,请稍候重试。";
      17. //返回码(200)
      18. private int code;
      19. //返回消息
      20. private String message;
      21. @ApiModelProperty(value = "返回数据(单条或多条)")
      22. private Map data = new HashMap();
      23. public ResponseUtil(int code, String message) {
      24. this.code=code;
      25. this.message=message;
      26. }
      27. public ResponseUtil(int code, String message, Map data) {
      28. this.code=code;
      29. this.message=message;
      30. this.data=data;
      31. }
      32. //对response写入Object数据
      33. public static void reponseOutDiy(HttpServletResponse response,int statusCode , Object result) {
      34. ObjectMapper mapper = new ObjectMapper();
      35. PrintWriter writer = null;
      36. response.setStatus(statusCode);
      37. response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
      38. try {
      39. writer = response.getWriter();
      40. mapper.writeValue(writer, result);
      41. writer.flush();
      42. } catch (IOException e) {
      43. e.printStackTrace();
      44. } finally {
      45. if (writer != null) {
      46. writer.flush();
      47. writer.close();
      48. }
      49. }
      50. }
      51. }

      2.4  TokenUtil

      1. import com.Lino_white.model.User; //model模块的user
      2. import io.jsonwebtoken.Claims;
      3. import io.jsonwebtoken.Jwts;
      4. import io.jsonwebtoken.SignatureAlgorithm;
      5. import org.springframework.util.StringUtils;
      6. import javax.servlet.http.HttpServletRequest;
      7. import java.text.ParseException;
      8. import java.text.SimpleDateFormat;
      9. import java.util.Date;
      10. import java.util.Locale;
      11. public class TokenUtil {
      12. public static final String APP_SECRET ="Lino_white"; //随便取,你的Token密钥
      13. public static final String TOKEN_HEAD="Authorization";
      14. public static final String TOKEN_PREFIX = "Bearer ";
      15. public static String createToken(User user){
      16. String token = Jwts.builder()
      17. .setId(String.valueOf(user.getId()))
      18. .setSubject(user.getUsername())
      19. .setIssuedAt(new Date()) //签发时间
      20. .setIssuer("Lino_white") //签发者
      21. .setExpiration(new Date(System.currentTimeMillis() + 300* 1000)) //过期时间 5分钟
      22. .signWith(SignatureAlgorithm.HS256, APP_SECRET) //签名算法跟密钥
      23. .claim("identity", user.getIdentity()) //可添加额外的属性
      24. .compact();
      25. return token;
      26. }
      27. //重新生成新的Token,异常时间由传入的参数决定
      28. public static String createToken(User user,Date expirationTime){
      29. SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      30. try {
      31. expirationTime= (Date) f.parse(f.format(expirationTime));
      32. } catch (ParseException e) {
      33. throw new RuntimeException(e);
      34. }
      35. String token = Jwts.builder()
      36. .setId(String.valueOf(user.getId()))
      37. .setSubject(user.getUsername())
      38. .setIssuedAt(new Date()) //签发时间
      39. .setIssuer("Lino_white") //签发者
      40. .setExpiration(expirationTime) //过期时间
      41. .signWith(SignatureAlgorithm.HS256, APP_SECRET) //签名算法跟密钥
      42. .claim("identity", user.getIdentity()) //可添加额外的属性
      43. .compact();
      44. return token;
      45. }
      46. //获得用户名
      47. public String getUsernameFromToken(String token){
      48. return Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token).getBody().getSubject();
      49. }
      50. /**
      51. * 判断token是否存在与有效(1)
      52. */
      53. public boolean checkToken(String token){
      54. if (StringUtils.isEmpty(token)) return false;
      55. try {
      56. Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token);
      57. }catch (Exception e){
      58. e.printStackTrace();
      59. return false;
      60. }
      61. return true;
      62. }
      63. /**
      64. * 判断token是否存在与有效(2)
      65. */
      66. public boolean checkToken(HttpServletRequest request){
      67. try {
      68. String token = request.getHeader("token");
      69. return checkToken(token);
      70. }catch (Exception e){
      71. e.printStackTrace();
      72. return false;
      73. }
      74. }
      75. //获得全部属性
      76. public Claims parseJwt(String token){
      77. Claims claims = Jwts.parser()
      78. .setSigningKey(APP_SECRET) // 设置标识名
      79. .parseClaimsJws(token) //解析token
      80. .getBody();
      81. return claims;
      82. }
      83. //获得指定属性
      84. public String getTokenClaim(String token,String key){
      85. Claims body = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token).getBody();
      86. return String.valueOf(body.get(key));
      87. }
      88. }

      3.model模块

      3.1 User

      1. import com.baomidou.mybatisplus.annotation.IdType;
      2. import com.baomidou.mybatisplus.annotation.TableId;
      3. import com.baomidou.mybatisplus.annotation.TableName;
      4. import lombok.Data;
      5. import java.io.Serializable;
      6. @Data
      7. //实体:用户
      8. @TableName("user")
      9. public class User implements Serializable {
      10. //用户id
      11. @TableId(type = IdType.AUTO)
      12. private Long id;
      13. //用户名
      14. private String username;
      15. //密码
      16. private String password;
      17. //身份
      18. private String identity;
      19. }

      4.service模块

      在这个模块下启动类需要开启SpringBoot:@SpringBootApplication

      pom.xml

      1. <dependency>
      2. <groupId>mysqlgroupId>
      3. <artifactId>mysql-connector-javaartifactId>
      4. dependency>
      5. <dependency>
      6. <groupId>com.baomidougroupId>
      7. <artifactId>mybatis-plus-boot-starterartifactId>
      8. dependency>
      9. <dependency>
      10. <groupId>com.alibabagroupId>
      11. <artifactId>fastjsonartifactId>
      12. dependency>
      13. <dependency>
      14. <groupId>org.springframework.bootgroupId>
      15. <artifactId>spring-boot-starter-webartifactId>
      16. dependency>
      17. <dependency>
      18. <groupId>org.springframeworkgroupId>
      19. <artifactId>spring-webartifactId>
      20. <version>5.3.22version>
      21. <scope>compilescope>
      22. dependency>
      23. <dependency>
      24. <groupId>com.Lino_whitegroupId>
      25. <artifactId>modelartifactId>
      26. <version>1.0-SNAPSHOTversion>
      27. <scope>compilescope>
      28. dependency>
      29. <dependency>
      30. <groupId>com.Lino_whitegroupId>
      31. <artifactId>commonartifactId>
      32. <version>1.0-SNAPSHOTversion>
      33. <scope>compilescope>
      34. dependency>
      35. <dependency>
      36. <groupId>com.Lino_whitegroupId>
      37. <artifactId>spring_securityartifactId>
      38. <version>1.0-SNAPSHOTversion>
      39. dependency>

      4.1 UserMapper

      1. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
      2. import com.Lino_white.model.User;
      3. import org.apache.ibatis.annotations.Select;
      4. public interface UserMapper extends BaseMapper {
      5. @Select("select * from user where username=#{username}")
      6. User findUserByName(String username);
      7. }

      4.2 重点说明

      这里采用的是SpringBoot的方式,所以相关的Service跟ServiceImpl省略,主要讲解多模块下的Security怎么运行登录跟认证。

      下面的4.3就是多模块的关键文件,在Service模块下继承了UserDetailsService

      4.3 MyUserDetailService

      1. import com.Lino_white.model.User;
      2. import com.Lino_white.service.UserService;
      3. import com.Lino_white.spring_security.DiyUserDetails;
      4. import org.springframework.beans.BeanUtils;
      5. import org.springframework.beans.factory.annotation.Autowired;
      6. import org.springframework.beans.factory.annotation.Qualifier;
      7. import org.springframework.security.core.authority.SimpleGrantedAuthority;
      8. import org.springframework.security.core.userdetails.UserDetails;
      9. import org.springframework.security.core.userdetails.UserDetailsService;
      10. import org.springframework.security.core.userdetails.UsernameNotFoundException;
      11. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
      12. import org.springframework.stereotype.Service;
      13. import java.util.ArrayList;
      14. import java.util.List;
      15. /**
      16. * 从数据库读取用户信息(用户名,密码,身份)进行身份认证
      17. */
      18. @Service("userDetailsService")
      19. public class MyUserDetailService implements UserDetailsService {
      20. @Autowired
      21. private UserService userService;
      22. @Override
      23. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      24. System.out.println("********开始loadUserByUsername********");
      25. User user = userService.findUserByName(username);
      26. System.out.println("浏览器的username:"+username);
      27. System.out.println("数据库的username:"+user.getUsername());
      28. if (user==null) throw new UsernameNotFoundException(username);
      29. //根据当前用户名查询用户权限
      30. List authorities=new ArrayList<>();
      31. authorities.add("ROLE_"+user.getIdentity());
      32. DiyUserDetails details=new DiyUserDetails();
      33. BeanUtils.copyProperties(user,details);
      34. details.setAuthorities(authorities);
      35. //如果数据库密码无加密,用下列
      36. //details.setPassword(new BCryptPasswordEncoder().encode(user.getPassword()));
      37. System.out.println("********结束loadUserByUsername********");
      38. return details;
      39. }
      40. }

      5.spring_security模块

      5.1 DiyUserDetails(UserDetails)

      1. import com.Lino_white.model.User;
      2. import lombok.Data;
      3. import lombok.EqualsAndHashCode;
      4. import org.springframework.security.core.GrantedAuthority;
      5. import org.springframework.security.core.authority.SimpleGrantedAuthority;
      6. import org.springframework.security.core.userdetails.UserDetails;
      7. import org.springframework.util.StringUtils;
      8. import java.io.Serializable;
      9. import java.util.ArrayList;
      10. import java.util.Collection;
      11. @Data
      12. @EqualsAndHashCode(callSuper = false)
      13. public class DiyUserDetails extends User implements UserDetails, Serializable {
      14. //用户权限列表
      15. private Collection authorities;
      16. @Override
      17. public Collectionextends GrantedAuthority> getAuthorities() {
      18. Collection authorities1 = new ArrayList<>();
      19. for(String permissionValue : authorities) {
      20. if(StringUtils.isEmpty(permissionValue)) continue;
      21. SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
      22. authorities1.add(authority);
      23. }
      24. return authorities1;
      25. }
      26. @Override
      27. public boolean isAccountNonExpired() {
      28. return true;
      29. }
      30. @Override
      31. public boolean isAccountNonLocked() {
      32. return true;
      33. }
      34. @Override
      35. public boolean isCredentialsNonExpired() {
      36. return true;
      37. }
      38. @Override
      39. public boolean isEnabled() {
      40. return true;
      41. }
      42. }

      5.2 WebSecurityConfig(WebSecurityConfigurerAdapter)

      1. import com.Lino_white.spring_security.TokenAuthenticationFilter;
      2. import com.Lino_white.spring_security.TokenLoginFilter;
      3. import com.Lino_white.spring_security.TokenOncePerRequestFilter;
      4. import com.Lino_white.spring_security.*;
      5. import org.springframework.beans.factory.annotation.Autowired;
      6. import org.springframework.context.annotation.Bean;
      7. import org.springframework.context.annotation.Configuration;
      8. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
      9. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
      10. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
      11. import org.springframework.security.config.annotation.web.builders.WebSecurity;
      12. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
      13. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
      14. import org.springframework.security.config.http.SessionCreationPolicy;
      15. import org.springframework.security.core.userdetails.UserDetailsService;
      16. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
      17. import org.springframework.security.crypto.password.PasswordEncoder;
      18. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
      19. @Configuration
      20. @EnableWebSecurity //开启Security功能
      21. @EnableGlobalMethodSecurity(prePostEnabled = true) //启动方法级别的权限认证
      22. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
      23. @Autowired
      24. private UserDetailsService myUserDetailService;
      25. @Bean
      26. //配置密码加密器
      27. public PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
      28. //配置哪些请求不拦截
      29. @Override
      30. public void configure(WebSecurity web) throws Exception {
      31. // web.ignoring().antMatchers("/index","/api/**");
      32. }
      33. @Override
      34. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      35. auth.userDetailsService(myUserDetailService).passwordEncoder(passwordEncoder());
      36. }
      37. //配置安全策略
      38. @Override
      39. protected void configure(HttpSecurity http) throws Exception {
      40. http.authorizeRequests()
      41. .antMatchers("/index").authenticated()
      42. .antMatchers("/user/**").authenticated()
      43. .anyRequest().permitAll()
      44. .and()
      45. .addFilterBefore(new TokenOncePerRequestFilter(myUserDetailService), TokenLoginFilter.class)
      46. //登录后,访问没有权限处理类
      47. .exceptionHandling().accessDeniedHandler(new NothingAccessDeniedHandler())
      48. //匿名访问,没有权限的处理类
      49. .authenticationEntryPoint(new LoginAuthenticationEntryPoint())
      50. //.anyRequest().authenticated()
      51. .and()
      52. .addFilter(new TokenLoginFilter(authenticationManager()))
      53. .addFilter(new TokenAuthenticationFilter(authenticationManager()))
      54. .formLogin()
      55. .successHandler(new LoginInSuccessHandler())
      56. .failureHandler(new LoginInFailHandler())
      57. // 不需要session
      58. .and()
      59. .logout()
      60. .logoutSuccessHandler(new LogOutSuccessHandler())
      61. .and()
      62. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
      63. .and()
      64. .csrf().disable()
      65. ;
      66. }
      67. }

      5.3 TokenAuthenticationFilter(BasicAuthenticationFilter)

      1. import com.Lino_white.common.TokenUtil;
      2. import org.springframework.security.authentication.AuthenticationManager;
      3. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
      4. import org.springframework.security.core.context.SecurityContextHolder;
      5. import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
      6. import javax.servlet.FilterChain;
      7. import javax.servlet.ServletException;
      8. import javax.servlet.http.HttpServletRequest;
      9. import javax.servlet.http.HttpServletResponse;
      10. import java.io.IOException;
      11. import java.util.ArrayList;
      12. public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
      13. public TokenAuthenticationFilter(AuthenticationManager authenticationManager) {
      14. super(authenticationManager);
      15. }
      16. @Override
      17. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
      18. String token = request.getHeader(TokenUtil.TOKEN_HEAD);
      19. if (token==null || !token.startsWith(TokenUtil.TOKEN_PREFIX)){
      20. // 如果请求头中没有Authorization信息则直接放行了
      21. chain.doFilter(request,response);
      22. return;
      23. }
      24. // 如果请求头中有token,则进行解析,并且设置认证信息
      25. String usernameFromToken = new TokenUtil().getUsernameFromToken(token.replace(TokenUtil.TOKEN_PREFIX,""));
      26. UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(usernameFromToken, null, new ArrayList<>());
      27. SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
      28. super.doFilterInternal(request, response, chain);
      29. }
      30. }

      5.4 TokenLoginFilter(UsernamePasswordAuthenticationFilter)

      1. import com.fasterxml.jackson.databind.ObjectMapper;
      2. import com.Lino_white.model.User;
      3. import com.Lino_white.spring_security.LoginInSuccessHandler;
      4. import org.springframework.beans.factory.annotation.Autowired;
      5. import org.springframework.security.authentication.AuthenticationManager;
      6. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
      7. import org.springframework.security.core.Authentication;
      8. import org.springframework.security.core.AuthenticationException;
      9. import org.springframework.security.core.authority.SimpleGrantedAuthority;
      10. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
      11. import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
      12. import org.springframework.stereotype.Component;
      13. import javax.servlet.FilterChain;
      14. import javax.servlet.ServletException;
      15. import javax.servlet.http.HttpServletRequest;
      16. import javax.servlet.http.HttpServletResponse;
      17. import java.io.IOException;
      18. import java.io.PrintWriter;
      19. import java.util.ArrayList;
      20. import java.util.HashMap;
      21. import java.util.List;
      22. import java.util.Map;
      23. /**
      24. *
      25. * 登录过滤器,继承UsernamePasswordAuthenticationFilter
      26. * 对用户名密码进行登录校验,或对用户密码进行私钥解密等操作
      27. *
      28. */
      29. public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
      30. private AuthenticationManager authenticationManager;
      31. public TokenLoginFilter(AuthenticationManager authenticationManager) {
      32. this.authenticationManager = authenticationManager;
      33. this.setPostOnly(false);
      34. // 认证路径 - 发送什么请求,就会进行认证
      35. this.setRequiresAuthenticationRequestMatcher(
      36. new AntPathRequestMatcher("/login", "POST")
      37. );
      38. }
      39. @Override
      40. public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
      41. try {
      42. String username = request.getParameter("username");
      43. String password = request.getParameter("password");
      44. System.out.println("进入UsernamePasswordAuthenticationFilter:username="+username+" password="+password);
      45. UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
      46. // usernamePasswordAuthenticationToken.setDetails(authenticationDetailsSource.buildDetails(request));
      47. this.setDetails(request, usernamePasswordAuthenticationToken);
      48. return authenticationManager.authenticate(usernamePasswordAuthenticationToken);
      49. } catch (Exception e) {
      50. throw new RuntimeException(e);
      51. }
      52. }
      53. @Override
      54. protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
      55. System.out.println("Filter认证成功------Start");
      56. User user = (User) authResult.getPrincipal();
      57. LoginInSuccessHandler loginInSuccessHandler = new LoginInSuccessHandler();
      58. loginInSuccessHandler.onAuthenticationSuccess(request, response, chain, authResult);
      59. System.out.println("Filter认证成功------End");
      60. }
      61. @Override
      62. protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
      63. System.out.println("Filter认证失败------");
      64. super.unsuccessfulAuthentication(request, response, failed);
      65. }
      66. }

      5.5 TokenOncePerRequestFilter(OncePerRequestFilter)

      1. import com.Lino_white.common.RedisUtil;
      2. import com.Lino_white.common.ResponseUtil;
      3. import com.Lino_white.common.TokenUtil;
      4. import com.Lino_white.spring_security.DiyUserDetails;
      5. import io.jsonwebtoken.Claims;
      6. import io.jsonwebtoken.ExpiredJwtException;
      7. import org.springframework.beans.factory.annotation.Autowired;
      8. import org.springframework.beans.factory.annotation.Qualifier;
      9. import org.springframework.data.redis.core.StringRedisTemplate;
      10. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
      11. import org.springframework.security.core.context.SecurityContextHolder;
      12. import org.springframework.security.core.userdetails.UserDetails;
      13. import org.springframework.security.core.userdetails.UserDetailsService;
      14. import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
      15. import org.springframework.stereotype.Component;
      16. import org.springframework.web.filter.OncePerRequestFilter;
      17. import sun.security.util.SecurityConstants;
      18. import javax.servlet.FilterChain;
      19. import javax.servlet.ServletException;
      20. import javax.servlet.http.HttpServletRequest;
      21. import javax.servlet.http.HttpServletResponse;
      22. import javax.xml.crypto.Data;
      23. import java.io.IOException;
      24. import java.text.ParseException;
      25. import java.text.SimpleDateFormat;
      26. import java.util.Date;
      27. /**
      28. * 过滤器,在请求过来的时候,解析请求头中的token,再解析token得到用户信息,再存到SecurityContextHolder中
      29. * @author Lino_white
      30. */
      31. @Component
      32. public class TokenOncePerRequestFilter extends OncePerRequestFilter {
      33. @Autowired
      34. UserDetailsService userDetailsService;
      35. StringRedisTemplate stringRedisTemplate=RedisUtil.stringRedisTemplateStatic;
      36. public TokenOncePerRequestFilter(UserDetailsService myUserDetailService) {
      37. userDetailsService=myUserDetailService;
      38. }
      39. @Override
      40. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
      41. SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      42. String authHeader = request.getHeader(TokenUtil.TOKEN_HEAD);
      43. if (authHeader != null && authHeader.startsWith(TokenUtil.TOKEN_PREFIX)) {
      44. final String authToken = authHeader.replace(TokenUtil.TOKEN_PREFIX,"");
      45. //这里的authToken可能时间已过,需要重新创建一个token
      46. //先对比redis中的过期时间,redis的过期时间随着用户的操作而更新,token可能没有及时更新
      47. //进行判断,要么token失效了,跳转重新登录,
      48. // 要么redis过期时间更新了,生成新的token返回给前端
      49. String username;
      50. Claims claims;
      51. DiyUserDetails userDetails;
      52. try {
      53. claims = new TokenUtil().parseJwt(authToken);
      54. username = claims.getSubject();
      55. } catch (ExpiredJwtException e) {
      56. //token过期
      57. claims = e.getClaims();
      58. username = claims.getSubject();
      59. if (RedisUtil.hasToken(username)) {
      60. Object expiration = stringRedisTemplate.opsForHash().get(username, "expiration");
      61. Object tokenExpirationTime=f.format(claims.getExpiration());
      62. Date expirationDate_redisTime=null,expirationDate_tokenTime=null,nowTime;
      63. try {
      64. expirationDate_redisTime= (Date) f.parseObject(String.valueOf(expiration));
      65. expirationDate_tokenTime= (Date) f.parseObject(String.valueOf(tokenExpirationTime));
      66. nowTime = (Date)f.parseObject(f.format(new Date()));
      67. } catch (ParseException ex) {
      68. throw new RuntimeException(ex);
      69. }
      70. System.out.println("*********Token过期(Start)***********");
      71. System.out.println("token浏览器过期时间:"+tokenExpirationTime);
      72. System.out.println("redis过期时间:"+expiration);
      73. /*
      74. * redis
      75. * token
      76. * */
      77. if (expirationDate_redisTime.getTime() < expirationDate_tokenTime.getTime() ||
      78. expirationDate_tokenTime.getTime() == expirationDate_redisTime.getTime() ||
      79. expirationDate_redisTime.getTime() < nowTime.getTime()) {
      80. //时间相同,跳转登录
      81. ResponseUtil.reponseOutDiy(response, 401, "用户已过期,请重新登录");
      82. System.out.println("*********Token过期(End)失效***********");
      83. return;
      84. } else {
      85. //时间不同,生成新token 需要用户id,身份,用户名
      86. //response存入token 返回
      87. Object expiration_redisTime = stringRedisTemplate.opsForHash().get(username, "expiration");
      88. Date date;
      89. try {
      90. date = (Date) f.parseObject(String.valueOf(expiration_redisTime));
      91. } catch (ParseException ex) {
      92. throw new RuntimeException(ex);
      93. }
      94. //通过数据库查询数据,创建token
      95. userDetails = (DiyUserDetails) userDetailsService.loadUserByUsername(username);
      96. String token = TokenUtil.createToken(userDetails,date);
      97. System.out.println("—————————————————start—————————————————————");
      98. System.out.println("token:"+token);
      99. RedisUtil.redis_SaveTokenInfo(token,username);
      100. response.setHeader(TokenUtil.TOKEN_HEAD,TokenUtil.TOKEN_PREFIX+token);
      101. request.setAttribute(TokenUtil.TOKEN_HEAD,TokenUtil.TOKEN_PREFIX+token);
      102. Date expiration1 = new TokenUtil().parseJwt(token).getExpiration();
      103. System.out.println("重新更新token后过期时间:"+expiration1);
      104. System.out.println("—————————————————End—————————————————————");
      105. ResponseUtil.reponseOutDiy(response, 200, token);
      106. System.out.println("*********Token过期(End)新Token***********");
      107. return;
      108. }
      109. }
      110. }
      111. if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
      112. System.out.println("OncePerRequestFilter 中的username :" +username);
      113. userDetails = (DiyUserDetails) userDetailsService.loadUserByUsername(username);
      114. if (userDetails != null) {
      115. UsernamePasswordAuthenticationToken authentication =
      116. new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
      117. authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
      118. SecurityContextHolder.getContext().setAuthentication(authentication);
      119. }
      120. }
      121. }
      122. chain.doFilter(request, response);
      123. }
      124. }

      5.6 LoginAuthenticationEntryPoint(AuthenticationEntryPoint)

      1. import com.Lino_white.common.ResponseUtil;
      2. import com.Lino_white.common.TokenUtil;
      3. import jdk.nashorn.internal.parser.Token;
      4. import org.springframework.security.core.AuthenticationException;
      5. import org.springframework.security.web.AuthenticationEntryPoint;
      6. import org.springframework.stereotype.Component;
      7. import javax.servlet.ServletException;
      8. import javax.servlet.http.HttpServletRequest;
      9. import javax.servlet.http.HttpServletResponse;
      10. import java.io.IOException;
      11. /**
      12. * 匿名未登录的时候访问,需要登录的资源的调用类
      13. * @author Lino_white
      14. */
      15. @Component
      16. public class LoginAuthenticationEntryPoint implements AuthenticationEntryPoint {
      17. @Override
      18. public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
      19. String token =httpServletRequest.getHeader(TokenUtil.TOKEN_HEAD);
      20. System.out.println("当前未登录,无法访问 ::"+token);
      21. if (token!=null && token.contains(TokenUtil.TOKEN_PREFIX)) {
      22. token=token.replace(TokenUtil.TOKEN_PREFIX,"");
      23. String usernameFromToken = new TokenUtil().getUsernameFromToken(token);
      24. System.out.println("用户名:"+usernameFromToken);
      25. }
      26. ResponseUtil.reponseOutDiy(httpServletResponse,401,"当前未登录,无法访问");
      27. }
      28. }

      5.7 LoginInFailHandler(AuthenticationFailureHandler)

      1. import com.Lino_white.common.ResponseUtil;
      2. import org.springframework.security.core.AuthenticationException;
      3. import org.springframework.security.web.authentication.AuthenticationFailureHandler;
      4. import org.springframework.stereotype.Component;
      5. import javax.servlet.ServletException;
      6. import javax.servlet.http.HttpServletRequest;
      7. import javax.servlet.http.HttpServletResponse;
      8. import java.io.IOException;
      9. /**
      10. * 登录账号密码错误等情况下,会调用的处理类
      11. * @author Lino_white
      12. */
      13. @Component
      14. public class LoginInFailHandler implements AuthenticationFailureHandler {
      15. @Override
      16. public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
      17. System.out.println("认证失败————————————");
      18. ResponseUtil.reponseOutDiy(httpServletResponse,401,"登录失败,请重试");
      19. }
      20. }

      5.8 LoginInSuccessHandler(AuthenticationSuccessHandler)

      1. import com.fasterxml.jackson.databind.ObjectMapper;
      2. import com.Lino_white.common.RedisUtil;
      3. import com.Lino_white.common.ResponseUtil;
      4. import com.Lino_white.common.TokenUtil;
      5. import com.Lino_white.model.User;
      6. import org.springframework.beans.factory.annotation.Autowired;
      7. import org.springframework.data.redis.core.StringRedisTemplate;
      8. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
      9. import org.springframework.security.core.Authentication;
      10. import org.springframework.security.core.context.SecurityContextHolder;
      11. import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
      12. import org.springframework.stereotype.Component;
      13. import javax.annotation.PostConstruct;
      14. import javax.servlet.FilterChain;
      15. import javax.servlet.ServletException;
      16. import javax.servlet.http.HttpServletRequest;
      17. import javax.servlet.http.HttpServletResponse;
      18. import java.io.IOException;
      19. import java.io.PrintWriter;
      20. import java.time.LocalDateTime;
      21. import java.util.HashMap;
      22. import java.util.Map;
      23. /**
      24. * 登录成功处理类,登录成功后会调用里面的方法
      25. * @author Lino_white
      26. */
      27. @Component
      28. public class LoginInSuccessHandler implements AuthenticationSuccessHandler {
      29. /**
      30. * 用户通过TokenLoginFilter(UsernamePasswordAuthenticationFilter)后,
      31. * 验证成功到这里进行 token创建,并将其存入redis
      32. *
      33. */
      34. @Override
      35. public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
      36. User user =(User) authentication.getPrincipal();
      37. String token = TokenUtil.createToken(user);
      38. //redis缓存token
      39. RedisUtil.redis_SaveTokenInfo(token,user.getUsername());
      40. //写入response
      41. response.setHeader("token", TokenUtil.TOKEN_PREFIX+token);
      42. try {
      43. //登录成功,返回json格式进行提示
      44. response.setContentType("application/json;charset=utf-8");
      45. response.setStatus(HttpServletResponse.SC_OK);
      46. PrintWriter out=response.getWriter();
      47. Map map=new HashMap(4);
      48. map.put("code",HttpServletResponse.SC_OK);
      49. map.put("message","登录成功");
      50. out.write(new ObjectMapper().writeValueAsString(map));
      51. out.flush();
      52. out.close();
      53. }catch (Exception e){
      54. e.printStackTrace();
      55. }
      56. response.setStatus(200);
      57. chain.doFilter(request, response);
      58. }
      59. @Override
      60. public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
      61. }
      62. }

      5.9 LogOutSuccessHandler(LogoutSuccessHandler)

      1. import com.Lino_white.common.RedisUtil;
      2. import com.Lino_white.common.ResponseUtil;
      3. import com.Lino_white.common.TokenUtil;
      4. import org.springframework.data.redis.core.StringRedisTemplate;
      5. import org.springframework.security.core.Authentication;
      6. import org.springframework.security.core.context.SecurityContextHolder;
      7. import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
      8. import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
      9. import org.springframework.stereotype.Component;
      10. import javax.servlet.ServletException;
      11. import javax.servlet.http.HttpServletRequest;
      12. import javax.servlet.http.HttpServletResponse;
      13. import java.io.IOException;
      14. @Component
      15. public class LogOutSuccessHandler implements LogoutSuccessHandler {
      16. private StringRedisTemplate stringRedisTemplate= RedisUtil.stringRedisTemplateStatic;
      17. @Override
      18. public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
      19. //用户退出登录
      20. System.out.println("LogoutSuccessHandler退出");
      21. String token=request.getHeader("token");
      22. if (token==null) token=request.getHeader(TokenUtil.TOKEN_HEAD);
      23. token=token.replace(TokenUtil.TOKEN_PREFIX,"");
      24. String username = new TokenUtil().getUsernameFromToken(token);
      25. Authentication au = SecurityContextHolder.getContext().getAuthentication();
      26. if (au!=null) new SecurityContextLogoutHandler().logout(request,response,au);
      27. Boolean delete = stringRedisTemplate.delete(username);
      28. if (delete) ResponseUtil.reponseOutDiy(response,200,"用户已成功退出");
      29. }
      30. }

      5.10 NothingAccessDeniedHandler(AccessDeniedHandler)

      1. import com.Lino_white.common.ResponseUtil;
      2. import org.springframework.security.access.AccessDeniedException;
      3. import org.springframework.security.web.access.AccessDeniedHandler;
      4. import org.springframework.stereotype.Component;
      5. import javax.servlet.ServletException;
      6. import javax.servlet.http.HttpServletRequest;
      7. import javax.servlet.http.HttpServletResponse;
      8. import java.io.IOException;
      9. /**
      10. * 没有权限,被拒绝访问时的调用类
      11. * @author Lino_white
      12. */
      13. @Component
      14. public class NothingAccessDeniedHandler implements AccessDeniedHandler {
      15. @Override
      16. public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
      17. System.out.println("没有权限");
      18. ResponseUtil.reponseOutDiy(httpServletResponse,403,"当前您没有该权限");
      19. }
      20. }

      6.测试

      这里是使用postman工具进行测试的,在这里之前已经在数据库有用户名跟密码都为user的数据,并且密码已是加密形式。

      1.首次访问index,如下图

      2.访问/login,并且提供相关参数,如下图:

      这时,redis数据库就有了用户名为 user的数据 

      3.在返回Headers信息中找到token,复制除了Bearer+空格以外的东西,如下图的蓝色底token

      4.将请求方式改成Get,在Authorization的Type中,选择Bearer Token,粘贴上刚才的Token,点击Send发送

       5.就会显示可以正常访问index页面了

      6.退出则为/logout ,带上参数username即可,请求方式为POST。

       同时,redis数据库中用户名为user的key值也被删除掉。

       

       

       

    48. 相关阅读:
      【论文复现|智能算法改进】基于多策略的改进蜜獾算法及其应用
      阿里终面:10亿数据如何快速插入MySQL?
      【EndNote X9.1 汉化版使用指南】
      重新定义分析 - EventBridge 实时事件分析平台发布
      尚医通_第11章_医院排班管理和搭建用户系统环境
      【Java UI】HarmonyOs如何集成Hawk
      SpringBoot 整合 Quartz 定时任务框架
      题目 1057: 二级C语言-分段函数
      Qt多线程之QThreadData::current()理解
      EEGLAB及其插件下载安装
    49. 原文地址:https://blog.csdn.net/white_mvlog/article/details/127589924