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

网友投稿 819 2023-02-11

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

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 生成时刻的时间戳进行比较,当当前时间的距离创建时间快要到达http://设置的redis过期时间的话,就重新设置token过期时间,将过期时间延长。

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

四、具体代码实现

1、自定义注解

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

@Retention(RetentionPolicy.RUNTIME)

public @interface AuthToken {

}

2、登陆控制器

@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、-

@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.com/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相关依赖

org.springframework.boot

spring-boot-starter-security

1.5.3.RELEASE

org.springframework

spring-webmvc

4.3.8.RELEASE

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

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 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 getAttributes(HttpServletRequest request);

}

三、权限管理-实现类 AuthenticationInterceptor

@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 getAttributes(HttpServletRequest request) {

return customUserService.getAttributes(request);

}

@Override

public void afterPropertiesSet() throws Exception {

}

}

四、用户Session信息管理类

集成redis维护用户session信息

@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

@Service

public class UserPermissionService {

@Resource

private SysUserDao userDao;

@Resource

private SysPermissionDao permissionDao;

private HashMap> map =null;

/**

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

*/

public void loadResourceDefine(){

map = new HashMap<>();

Collection array;

ConfigAttribute cfg;

List 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 getAuthoritiesById(String userId) {

SysUshttp://erRspDTO user = userDao.findById(userId);

if (user != null) {

List permissions = permissionDao.findByAdminUserId(user.getUserId());

List 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 getAttributes(HttpServletRequest request) throws IllegalArgumentException {

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

loadResourceDefine();

AntPathRequestMatcher matcher;

String resUrl;

for(Iterator 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中所需的权限,判断用户是否具有请求当前资源或者执行当前操作的权限。

@Service

public class AccessDecisionManager {

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

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

return true;

}

ConfigAttribute c;

String needRole;

for(Iterator 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;

}

}

七、配置拦截规则

@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

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

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

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

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

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

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:web前端安全揭秘(web前端安全揭秘怎么写)
下一篇:web前端安全编码规范(web安全标准)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~