好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

SpringBoot和Redis实现Token权限认证的实例讲解

一、引言

登陆权限控制是每个系统都应必备的功能,实现方法也有好多种。下面使用Token认证来实现系统的权限访问。

功能描述:

用户登录成功后,后台返回一个token给调用者,同时自定义一个@AuthToken注解,被该注解标注的API请求都需要进行token效验,效验通过才可以正常访问,实现接口级的鉴权控制。

同时token具有生命周期,在用户持续一段时间不进行操作的话,token则会过期,用户一直操作的话,则不会过期。

二、环境

SpringBoot

Redis(Docke中镜像)

MySQL(Docker中镜像)

三、流程分析

1、流程分析

(1)、客户端登录,输入用户名和密码,后台进行验证,如果验证失败则返回登录失败的提示。

如果验证成功,则生成 token 然后将 username 和 token 双向绑定 (可以根据 username 取出 token 也可以根据 token 取出username)存入redis,同时使用 token+username 作为key把当前时间戳也存入redis。并且给它们都设置过期时间。

(2)、每次请求接口都会走拦截器,如果该接口标注了@AuthToken注解,则要检查客户端传过来的Authorization字段,获取 token。

由于 token 与 username 双向绑定,可以通过获取的 token 来尝试从 redis 中获取 username,如果可以获取则说明 token 正确,反之,说明错误,返回鉴权失败。

(3)、token可以根据用户使用的情况来动态的调整自己过期时间。

在生成 token 的同时也往 redis 里面存入了创建 token 时的时间戳,每次请求被拦截器拦截 token 验证成功之后,将当前时间与存在 redis 里面的 token 生成时刻的时间戳进行比较,当当前时间的距离创建时间快要到达设置的redis过期时间的话,就重新设置token过期时间,将过期时间延长。

如果用户在设置的 redis 过期时间的时间长度内没有进行任何操作(没有发请求),则token会在redis中过期。

四、具体代码实现

1、自定义注解

?

1

2

3

4

@Target ({ElementType.METHOD, ElementType.TYPE})

@Retention (RetentionPolicy.RUNTIME)

public @interface AuthToken {

}

2、登陆控制器

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

@RestController

public class welcome {

  Logger logger = LoggerFactory.getLogger(welcome. class );

  @Autowired

  Md5TokenGenerator tokenGenerator;

  @Autowired

  UserMapper userMapper;

  @GetMapping ( "/welcome" )

  public String welcome(){

   return "welcome token authentication" ;

  }

  @RequestMapping (value = "/login" , method = RequestMethod.GET)

  public ResponseTemplate login(String username, String password) {

   logger.info( "username:" +username+ "  password:" +password);

   User user = userMapper.getUser(username,password);

   logger.info( "user:" +user);

   JSONObject result = new JSONObject();

   if (user != null ) {

    Jedis jedis = new Jedis( "192.168.1.106" , 6379 );

    String token = tokenGenerator.generate(username, password);

    jedis.set(username, token);

    //设置key生存时间,当key过期时,它会被自动删除,时间是秒

    jedis.expire(username, ConstantKit.TOKEN_EXPIRE_TIME);

    jedis.set(token, username);

    jedis.expire(token, ConstantKit.TOKEN_EXPIRE_TIME);

    Long currentTime = System.currentTimeMillis();

    jedis.set(token + username, currentTime.toString());

    //用完关闭

    jedis.close();

    result.put( "status" , "登录成功" );

    result.put( "token" , token);

   } else {

    result.put( "status" , "登录失败" );

   }

   return ResponseTemplate.builder()

     .code( 200 )

     .message( "登录成功" )

     .data(result)

     .build();

  }

  //测试权限访问

  @RequestMapping (value = "test" , method = RequestMethod.GET)

  @AuthToken

  public ResponseTemplate test() {

   logger.info( "已进入test路径" );

   return ResponseTemplate.builder()

     .code( 200 )

     .message( "Success" )

     .data( "test url" )

     .build();

  }

}

3、拦截器

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

@Slf4j

public class AuthorizationInterceptor implements HandlerInterceptor {

  //存放鉴权信息的Header名称,默认是Authorization

  private String httpHeaderName = "Authorization" ;

  //鉴权失败后返回的错误信息,默认为401 unauthorized

  private String unauthorizedErrorMessage = "401 unauthorized" ;

  //鉴权失败后返回的HTTP错误码,默认为401

  private int unauthorizedErrorCode = HttpServletResponse.SC_UNAUTHORIZED;

  //存放登录用户模型Key的Request Key

  public static final String REQUEST_CURRENT_KEY = "REQUEST_CURRENT_KEY" ;

  @Override

  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

   if (!(handler instanceof HandlerMethod)) {

    return true ;

   }

   HandlerMethod handlerMethod = (HandlerMethod) handler;

   Method method = handlerMethod.getMethod();

   // 如果打上了AuthToken注解则需要验证token

   if (method.getAnnotation(AuthToken. class ) != null || handlerMethod.getBeanType().getAnnotation(AuthToken. class ) != null ) {

    String token = request.getParameter(httpHeaderName);

    log.info( "Get token from request is {} " , token);

    String username = "" ;

    Jedis jedis = new Jedis( "192.168.1.106" , 6379 );

    if (token != null && token.length() != 0 ) {

     username = jedis.get(token);

     log.info( "Get username from Redis is {}" , username);

    }

    if (username != null && !username.trim().equals( "" )) {

     Long tokeBirthTime = Long.valueOf(jedis.get(token + username));

     log.info( "token Birth time is: {}" , tokeBirthTime);

     Long diff = System.currentTimeMillis() - tokeBirthTime;

     log.info( "token is exist : {} ms" , diff);

     if (diff > ConstantKit.TOKEN_RESET_TIME) {

      jedis.expire(username, ConstantKit.TOKEN_EXPIRE_TIME);

      jedis.expire(token, ConstantKit.TOKEN_EXPIRE_TIME);

      log.info( "Reset expire time success!" );

      Long newBirthTime = System.currentTimeMillis();

      jedis.set(token + username, newBirthTime.toString());

     }

     //用完关闭

     jedis.close();

     request.setAttribute(REQUEST_CURRENT_KEY, username);

     return true ;

    } else {

     JSONObject jsonObject = new JSONObject();

     PrintWriter out = null ;

     try {

      response.setStatus(unauthorizedErrorCode);

      response.setContentType(MediaType.APPLICATION_JSON_VALUE);

      jsonObject.put( "code" , ((HttpServletResponse) response).getStatus());

      jsonObject.put( "message" , HttpStatus.UNAUTHORIZED);

      out = response.getWriter();

      out.println(jsonObject);

      return false ;

     } catch (Exception e) {

      e.printStackTrace();

     } finally {

      if ( null != out) {

       out.flush();

       out.close();

      }

     }

    }

   }

   request.setAttribute(REQUEST_CURRENT_KEY, null );

   return true ;

  }

  @Override

  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

  }

  @Override

  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

  }

}

4、测试结果

五、小结

登陆权限控制,实际上利用的就是拦截器的拦截功能。因为每一次请求都要通过拦截器,只有拦截器验证通过了,才能访问想要的请求路径,所以在拦截器中做校验Token校验。

想要代码,可以去GitHub上查看。

https://github测试数据/Hofanking/token-authentication.git

拦截器介绍,可以参考 这篇文章

补充:springboot+spring security+redis实现登录权限管理

笔者负责的电商项目的技术体系是基于SpringBoot,为了实现一套后端能够承载ToB和ToC的业务,需要完善现有的权限管理体系。

在查看Shiro和Spring Security对比后,笔者认为Spring Security更加适合本项目使用,可以总结为以下2点:

1、基于拦截器的权限校验逻辑,可以针对ToB的业务接口来做相关的权限校验,以笔者的项目为例,ToB的接口请求路径以/openshop/api/开头,可以根据接口请求路径配置全局的ToB的拦截器;

2、Spring Security的权限管理模型更简单直观,对权限、角色和用户做了很好的解耦。

以下介绍本项目的实现步骤

一、在项目中添加Spring相关依赖

?

1

2

3

4

5

6

7

8

9

10

< dependency >

   < groupId >org.springframework.boot</ groupId >

   < artifactId >spring-boot-starter-security</ artifactId >

   < version >1.5.3.RELEASE</ version >

  </ dependency >

  < dependency >

   < groupId >org.springframework</ groupId >

   < artifactId >spring-webmvc</ artifactId >

   < version >4.3.8.RELEASE</ version >

  </ dependency >

二、使用模板模式定义权限管理拦截器抽象类

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

public abstract class AbstractAuthenticationInterceptor extends HandlerInterceptorAdapter implements InitializingBean {

  @Resource

  private AccessDecisionManager accessDecisionManager;

  @Override

  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

   //检查是否登录

   String userId = null ;

   try {

    userId = getUserId();

   } catch (Exception e){

    JsonUtil.renderJson(response, 403 , "{}" );

    return false ;

   }

   if (StringUtils.isEmpty(userId)){

    JsonUtil.renderJson(response, 403 , "{}" );

    return false ;

   }

   //检查权限

   Collection<? extends GrantedAuthority> authorities = getAttributes(userId);

   Collection<ConfigAttribute> configAttributes = getAttributes(request);

   return accessDecisionManager.decide(authorities,configAttributes);

  }

  //获取用户id

  public abstract String getUserId();

  //根据用户id获取用户的角色集合

  public abstract Collection<? extends GrantedAuthority> getAttributes(String userId);

  //查询请求需要的权限

  public abstract Collection<ConfigAttribute> getAttributes(HttpServletRequest request);

}

三、权限管理拦截器实现类 AuthenticationInterceptor

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

@Component

public class AuthenticationInterceptor extends AbstractAuthenticationInterceptor {

  @Resource

  private SessionManager sessionManager;

  @Resource

  private UserPermissionService customUserService;

  @Override

  public String getUserId() {

   return sessionManager.obtainUserId();

  }

  @Override

  public Collection<? extends GrantedAuthority> getAttributes(String s) {

   return customUserService.getAuthoritiesById(s);

  }

  @Override

  public Collection<ConfigAttribute> getAttributes(HttpServletRequest request) {

   return customUserService.getAttributes(request);

  }

  @Override

  public void afterPropertiesSet() throws Exception {

  }

}

四、用户Session信息管理类

集成redis维护用户session信息

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

@Component

public class SessionManager {

  private static final Logger logger = LoggerFactory.getLogger(SessionManager. class );

  @Autowired

  private RedisUtils redisUtils;

  public SessionManager() {

  }

  public UserInfoDTO obtainUserInfo() {

   UserInfoDTO userInfoDTO = null ;

   try {

    String token = this .obtainToken();

    logger.info( "=======token=========" , token);

    if (StringUtils.isEmpty(token)) {

     LemonException.throwLemonException(AccessAuthCode.sessionExpired.getCode(), AccessAuthCode.sessionExpired.getDesc());

    }

    userInfoDTO = (UserInfoDTO) this .redisUtils.obtain( this .obtainToken(), UserInfoDTO. class );

   } catch (Exception var3) {

    logger.error( "obtainUserInfo ex:" , var3);

   }

   if ( null == userInfoDTO) {

    LemonException.throwLemonException(AccessAuthCode.sessionExpired.getCode(), AccessAuthCode.sessionExpired.getDesc());

   }

   return userInfoDTO;

  }

  public String obtainUserId() {

   return this .obtainUserInfo().getUserId();

  }

  public String obtainToken() {

   HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();

   String token = request.getHeader( "token" );

   return token;

  }

  public UserInfoDTO createSession(UserInfoDTO userInfoDTO, long expired) {

   String token = UUIDUtil.obtainUUID( "token." );

   userInfoDTO.setToken(token);

   if (expired == 0L) {

    this .redisUtils.put(token, userInfoDTO);

   } else {

    this .redisUtils.put(token, userInfoDTO, expired);

   }

   return userInfoDTO;

  }

  public void destroySession() {

   String token = this .obtainToken();

   if (StringUtils.isNotBlank(token)) {

    this .redisUtils.remove(token);

   }

  }

}

五、用户权限管理service

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

@Service

public class UserPermissionService {

  @Resource

  private SysUserDao userDao;

  @Resource

  private SysPermissionDao permissionDao;

  private HashMap<String, Collection<ConfigAttribute>> map = null ;

  /**

   * 加载资源,初始化资源变量

   */

  public void loadResourceDefine(){

   map = new HashMap<>();

   Collection<ConfigAttribute> array;

   ConfigAttribute cfg;

   List<SysPermission> permissions = permissionDao.findAll();

   for (SysPermission permission : permissions) {

    array = new ArrayList<>();

    cfg = new SecurityConfig(permission.getName());

    array.add(cfg);

    map.put(permission.getUrl(), array);

   }

  }

/*

*

  * @Author zhangs

  * @Description 获取用户权限列表

  * @Date 18:56 2019/11/11

  **/

  public List<GrantedAuthority> getAuthoritiesById(String userId) {

   SysUserRspDTO user = userDao.findById(userId);

   if (user != null) {

    List<SysPermission> permissions = permissionDao.findByAdminUserId(user.getUserId());

    List<GrantedAuthority> grantedAuthorities = new ArrayList <>();

    for (SysPermission permission : permissions) {

     if (permission != null && permission.getName()!=null) {

      GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName());

      grantedAuthorities.add(grantedAuthority);

     }

    }

    return grantedAuthorities;

   }

   return null;

  }

  /*

  *

   * @Author zhangs

   * @Description 获取当前请求所需权限

   * @Date 18:57 2019/11/11

   **/

  public Collection<ConfigAttribute> getAttributes(HttpServletRequest request) throws IllegalArgumentException {

   if (map != null ) map.clear();

   loadResourceDefine();

   AntPathRequestMatcher matcher;

   String resUrl;

   for (Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) {

    resUrl = iter.next();

    matcher = new AntPathRequestMatcher(resUrl);

    if (matcher.matches(request)) {

     return map.get(resUrl);

    }

   }

   return null ;

  }

}

六、权限校验类 AccessDecisionManager

通过查看authorities中的权限列表是否含有configAttributes中所需的权限,判断用户是否具有请求当前资源或者执行当前操作的权限。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

@Service

public class AccessDecisionManager {

  public boolean decide(Collection<? extends GrantedAuthority> authorities, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {

   if ( null == configAttributes || configAttributes.size() <= 0 ) {

    return true ;

   }

   ConfigAttribute c;

   String needRole;

   for (Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {

    c = iter.next();

    needRole = c.getAttribute();

    for (GrantedAuthority ga : authorities) {

     if (needRole.trim().equals(ga.getAuthority())) {

      return true ;

     }

    }

   }

   return false ;

  }

}

七、配置拦截规则

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@Configuration

public class WebAppConfigurer extends WebMvcConfigurerAdapter {

  @Resource

  private AbstractAuthenticationInterceptor authenticationInterceptor;

  @Override

  public void addInterceptors(InterceptorRegistry registry) {

   // 多个拦截器组成一个拦截器链

   // addPathPatterns 用于添加拦截规则

   // excludePathPatterns 用户排除拦截

   //对来自/openshop/api/** 这个链接来的请求进行拦截

   registry.addInterceptor(authenticationInterceptor).addPathPatterns( "/openshop/api/**" );

   super .addInterceptors(registry);

  }

}

八 相关表说明

用户表 sys_user

?

1

2

3

4

5

6

7

8

9

10

11

CREATE TABLE `sys_user` (

  `user_id` varchar (64) NOT NULL COMMENT '用户ID' ,

  `username` varchar (255) DEFAULT NULL COMMENT '登录账号' ,

  `first_login` datetime(6) NOT NULL COMMENT '首次登录时间' ,

  `last_login` datetime(6) NOT NULL COMMENT '上次登录时间' ,

  `pay_pwd` varchar (100) DEFAULT NULL COMMENT '支付密码' ,

  `chant_id` varchar (64) NOT NULL DEFAULT '-1' COMMENT '关联商户id' ,

  `create_time` datetime DEFAULT NULL COMMENT '创建时间' ,

  `modify_time` datetime DEFAULT NULL COMMENT '修改时间' ,

  PRIMARY KEY (`user_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

角色表 sys_role

?

1

2

3

4

5

6

7

CREATE TABLE `sys_role` (

  `role_id` int (11) NOT NULL AUTO_INCREMENT,

  ` name ` varchar (255) DEFAULT NULL ,

  `create_time` datetime DEFAULT NULL COMMENT '创建时间' ,

  `modify_time` datetime DEFAULT NULL COMMENT '修改时间' ,

  PRIMARY KEY (`role_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

用户角色关联表 sys_role_user

?

1

2

3

4

5

6

CREATE TABLE `sys_role_user` (

  `id` int (11) NOT NULL AUTO_INCREMENT,

  `sys_user_id` varchar (64) DEFAULT NULL ,

  `sys_role_id` int (11) DEFAULT NULL ,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

权限表 sys_premission

?

1

2

3

4

5

6

7

8

9

10

11

CREATE TABLE `sys_permission` (

  `permission_id` int (11) NOT NULL ,

  ` name ` varchar (255) DEFAULT NULL COMMENT '权限名称' ,

  `description` varchar (255) DEFAULT NULL COMMENT '权限描述' ,

  `url` varchar (255) DEFAULT NULL COMMENT '资源url' ,

  `check_pwd` int (2) NOT NULL DEFAULT '1' COMMENT '是否检查支付密码:0不需要 1 需要' ,

  `check_sms` int (2) NOT NULL DEFAULT '1' COMMENT '是否校验短信验证码:0不需要 1 需要' ,

  `create_time` datetime DEFAULT NULL COMMENT '创建时间' ,

  `modify_time` datetime DEFAULT NULL COMMENT '修改时间' ,

  PRIMARY KEY (`permission_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

角色权限关联表 sys_permission_role

?

1

2

3

4

5

6

CREATE TABLE `sys_permission_role` (

  `id` int (11) NOT NULL AUTO_INCREMENT,

  `role_id` int (11) DEFAULT NULL ,

  `permission_id` int (11) DEFAULT NULL ,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。如有错误或未考虑完全的地方,望不吝赐教。

原文链接:https://blog.csdn.net/zxd1435513775/article/details/86555130

查看更多关于SpringBoot和Redis实现Token权限认证的实例讲解的详细内容...

  阅读:25次