精华内容
下载资源
问答
  • 今天用Java在后台写一个返回值Map<String,String>,怎么做都报错,最后才发现忘了加英文状态的逗号“,”记录下太蠢了

    今天用Java在后台写一个返回值Map<String,String>,怎么做都报错,最后才发现忘了加英文状态的逗号“,”

    记录下太蠢了


    展开全文
  • extern char tokenString[MAXTOKENLEN+1]; /** 函数getToken返回源程序中的下一个token*/ TokenType getToken(void); #endif scan.c #include "globals.h" #include "scan.h" /**定义的状态 */ ...

    mian.c

    #include "globals.h"
    
    /**创建只扫描的编译器 **/
    #define NO_PARSE TRUE
    
    #include "scan.h"///词法分析,核心过程为扫描函数 //如果NO_PARSE为true,调用头文件scan.h
    
    /** 分配全局变量 */
    int lineno = 0;
    FILE * source; ///源代码文件
    FILE * listing; ///显示分析过程的文件,这里重定向到stdout
    FILE * code; ///目标汇编代码文件
    
    /** 分配并初始化追踪标志 / 直接运行main.c看不到结果  需将下列部分变量的值从FALSE改为TRUE,表示分部执行相应操作 */
    int EchoSource = TRUE;  ///将TINY源程序回显到带有行号的列表-->改为true才能执行
    int TraceScan = TRUE;  ///当扫描程序识别出记号时,就显示每个记号的信息
    
    main( int argc, char * argv[] )
    {
      char pgm[120]; /** 源代码文件名称 */
      if (argc != 2)
        { fprintf(stderr,"usage: %s <filename>\n",argv[0]);
          exit(1); ///如果argv不为2,打印显示信息并退出【意为如果输入不是:tiny 文件名.文件类型 假如多输入了个1就表示有三个参数,则错误】
        }
      strcpy(pgm,argv[1]) ; ///复制argv[1]地址以null为退出字符的存储器区块到另一个存储器区块品pgm内
      if (strchr (pgm, '.') == NULL)
         strcat(pgm,".tny"); ///把.tny文件所指字符串添加到pgm结尾处并添加'\0'
      source = fopen(pgm,"r"); ///以只读的方式打开pgm文件,并将指向pgm文件的指针返回给source
      if (source==NULL)
      { fprintf(stderr,"File %s not found\n",pgm);
        exit(1); ///  如果源文件没有被找到,输出文件没有被找到
      }
      listing = stdout; /** 发清单消息到屏幕 */
      fprintf(listing,"\nTINY COMPILATION: %s\n",pgm); ///应答显示语句
    
    
    while (getToken()!=ENDFILE); ///如果输入流没有结束就继续进行循环,直至结束
    
      fclose(source); ///关闭源代码文件
      return 0;
    }
    
    globals.h
    ///全局定义.包括了数据类型的定义和整个编译器均使用的全程变量,任何代码文件都包含了globals.h头文件
    
    #ifndef _GLOBALS_H_
    #define _GLOBALS_H_  ///宏定义
    
    #include <stdio.h>
    #include <string.h> ///头文件引用
    
    #ifndef FALSE
    #define FALSE 0 //定义FALSE为0
    #endif
    
    #ifndef TRUE
    #define TRUE 1 //定义TRUE为1
    #endif
    
    ///定义了关键字个数8个
    #define MAXRESERVED 8
    
    typedef enum
        /* book-keeping tokens */
       {ENDFILE,ERROR,
        /* 保留字 */
        IF,THEN,ELSE,END,REPEAT,UNTIL,READ,WRITE,
        /* 标识符 */
        ID,NUM,
        /* 特殊符号 */
        ASSIGN,EQ,LT,PLUS,MINUS,TIMES,OVER,LPAREN,RPAREN,SEMI
       } TokenType;  /// 定义了关键字,运算符等内容的枚举值
    
    extern FILE* source; /* 源代码文本文件 */ ///extern用在变量或函数前,表示变量或函数的定义在其他文件中
    extern FILE* listing; /// 显示分析过程的文件的地址 */
    
    
    extern int lineno; /* 源代码行数清单 */
    
    /* EchoSource = TRUE causes the source program to
     * be echoed to the listing file with line numbers
     * during parsing
     */
    extern int EchoSource;
    
    /* TraceScan = TRUE causes token information to be
     * printed to the listing file as each token is
     * recognized by the scanner
     */
    extern int TraceScan;
    
    #endif
    

    scan.h
    ///对于tiny编译器的扫描程序接口
    
    #ifndef _SCAN_H_
    #define _SCAN_H_
    
    /** maxtokenlen是token的最大大小*/
    #define MAXTOKENLEN 40
    
    /** tokenString数组保存每个token */
    extern char tokenString[MAXTOKENLEN+1];
    
    /** 函数getToken返回源程序中的下一个token*/
    TokenType getToken(void);
    
    
    #endif
    

    scan.c

    #include "globals.h"
    #include "scan.h"
    
    
    /**定义的状态 */
    typedef enum
       { START,///初始状态
       INASSIGN,///进入到赋值状态
       INCOMMENT,///进入到注释状态
       INNUM,///进入到数字状态
       INID,///进入到标志符状态
       DONE  ///状态结束
       }StateType; /**每当语法分析程序需要一个单词时,就调用该子程序,得到 (类别码,单词的值)*/
    
    
    
    
    /** 语义标识符和保留字*/
    char tokenString[MAXTOKENLEN+1];
    
    
    
    
    /** BUFLEN = 源代码的输入缓冲长度 */
    #define BUFLEN 256
    
    
    static char lineBuf[BUFLEN]; /** 当前行 */
    static int linepos = 0; /** 在linebuf中的当前位置*/
    static int bufsize = 0;  /** 缓冲区的字符串当前大小*/
    static int EOF_flag = FALSE; /** 如果读入下一个字符出错,设置EOF_flag为假。*/
    
    
    /** getNextChar获取下一个非空白字符开始从lineBuf,如果读完,则读入新行*/
    static int getNextChar(void)
    { if (!(linepos < bufsize))
      { lineno++;
        if (fgets(lineBuf,BUFLEN-1,source))
        { if (EchoSource) fprintf(listing,"%4d: %s",lineno,lineBuf);
          bufsize = strlen(lineBuf);
          linepos = 0;
          return lineBuf[linepos++];
        }
        else
        { EOF_flag = TRUE;
          return EOF;
        }
      }
      else return lineBuf[linepos++];
    }
    
    
    
    /** 如果读入下一个字符出错,在linebuf中回退一个字符 。*/
    static void ungetNextChar(void)
    { if (!EOF_flag) linepos-- ;}
    
    
    /** 保留字的查找表 */
    static struct
        { char* str;
          TokenType tok;
        } reservedWords[MAXRESERVED]
       = {{"if",IF},{"then",THEN},{"else",ELSE},{"end",END},
          {"repeat",REPEAT},{"until",UNTIL},{"read",READ},
          {"write",WRITE}};
    
    
    
    
     /** 标识符是否是保留字*/
    static TokenType reservedLookup (char * s)
    { int i;
      for (i=0;i<MAXRESERVED;i++)
        if (!strcmp(s,reservedWords[i].str))
          return reservedWords[i].tok;
      return ID;
    }
    
    
    /****************************************/
    /**********扫描仪的主要功能*************/
    /****************************************/
    
    
    
    
    /** 函数gettoken返回源文件中下一个标记 */
    TokenType getToken(void)
    {  /** 存入tokenstring的位置 */
       int tokenStringIndex = 0;
       /** 保存当前要返回的token; */
       TokenType currentToken;
       /** 当前状态 */
       StateType state = START;
       /** 表示保存到tokenstring的flag */
       int save;
       while (state != DONE)
       { int c = getNextChar(); /**从输入buf中读入一个字符*/
         save = TRUE;
         switch (state)
         { case START:
             if (isdigit(c)) /**判断字母*/
               state = INNUM;
             else if (isalpha(c))
               state = INID;
             else if (c == ':')
               state = INASSIGN;
             else if ((c == ' ') || (c == '\t') || (c == '\n'))
               save = FALSE;
             else if (c == '{')
             { save = FALSE;
               state = INCOMMENT;
             }
             else
             { state = DONE;
               switch (c)
               { case EOF:
                   save = FALSE;
                   currentToken = ENDFILE;
                   break;
                 case '=':
                   currentToken = EQ;
                   break;
                 case '<':
                   currentToken = LT;
                   break;
                 case '+':
                   currentToken = PLUS;
                   break;
                 case '-':
                   currentToken = MINUS;
                   break;
                 case '*':
                   currentToken = TIMES;
                   break;
                 case '/':
                   currentToken = OVER;
                   break;
                 case '(':
                   currentToken = LPAREN;
                   break;
                 case ')':
                   currentToken = RPAREN;
                   break;
                 case ';':
                   currentToken = SEMI;
                   break;
                 default:
                   currentToken = ERROR;
                   break;
               }
             }
             break;
           case INCOMMENT:
             save = FALSE;
             if (c == EOF)
             { state = DONE;
               currentToken = ENDFILE;
             }
             else if (c == '}') state = START;
             break;
           case INASSIGN:
             state = DONE;
             if (c == '=')
               currentToken = ASSIGN;
             else
             {  /** 在输入中备份 */
               ungetNextChar();
               save = FALSE;
               currentToken = ERROR;
             }
             break;
           case INNUM:
             if (!isdigit(c))
             {  /** 在输入中备份*/
               ungetNextChar();
               save = FALSE;
               state = DONE;
               currentToken = NUM;
             }
             break;
           case INID:
             if (!isalpha(c))
             {  /** 在输入中备份*/
               ungetNextChar();
               save = FALSE;
               state = DONE;
               currentToken = ID;
             }
             break;
           case DONE:
           default: /** 应该不会执行 */
             fprintf(listing,"Scanner Bug: state= %d\n",state);
             state = DONE;
             currentToken = ERROR;
             break;
         }
         if ((save) && (tokenStringIndex <= MAXTOKENLEN))
           tokenString[tokenStringIndex++] = (char) c;
         if (state == DONE)
         { tokenString[tokenStringIndex] = '\0';
           if (currentToken == ID)
             currentToken = reservedLookup(tokenString);
         }
       }
       if (TraceScan) {
         fprintf(listing,"\t%d: ",lineno);
         printToken(currentToken,tokenString);
       }
       return currentToken;
    } /* end getToken */
    
    
    ///此函数输出一个标号和一个词素
    void printToken( TokenType token, const char* tokenString )
    { switch (token)
      { case IF:
        case THEN:
        case ELSE:
        case END:
        case REPEAT:
        case UNTIL:
        case READ:
        case WRITE:
          fprintf(listing,
             "reserved word: %s\n",tokenString);
          break;
        case ASSIGN: fprintf(listing,":=\n"); break;
        case LT: fprintf(listing,"<\n"); break;
        case EQ: fprintf(listing,"=\n"); break;
        case LPAREN: fprintf(listing,"(\n"); break;
        case RPAREN: fprintf(listing,")\n"); break;
        case SEMI: fprintf(listing,";\n"); break;
        case PLUS: fprintf(listing,"+\n"); break;
        case MINUS: fprintf(listing,"-\n"); break;
        case TIMES: fprintf(listing,"*\n"); break;
        case OVER: fprintf(listing,"/\n"); break;
        case ENDFILE: fprintf(listing,"EOF\n"); break;
        case NUM:
          fprintf(listing,
              "NUM, val= %s\n",tokenString);
          break;
        case ID:
          fprintf(listing,
              "ID, name= %s\n",tokenString);
          break;
        case ERROR:
          fprintf(listing,
              "ERROR: %s\n",tokenString);
          break;
        default: /* should never happen */
          fprintf(listing,"Unknown token: %d\n",token);
      }
    }


    ---->>>>>>用C语言的编译工具创建一个tiny项目,在创建这几个文件,在生成的tiny软件目录,写一个需要识别的txt文本文件,用控制台来到当前目录,输入tiny + 文件名.txt即可看到识别出的效果

    效果图:


    展开全文
  • String token = sysUserService.authLogin(username, password); Subject subject = SecurityUtils.getSubject(); subject.login(token);//login(org.apache.shiro.authc.AuthenticationToken) in Subject ...
  • Spring boot 入门教程-token验证

    万次阅读 热门讨论 2018-07-05 11:24:19
    我们在做项目时比如商城项目,有的页面的打开是需要登陆的而有的页面则不需要,所以这里就需要一种验证是否登录,或者登录是否过期,这里说一种token令牌+拦截器的方式。 生成token 使用JWT。 1.引入 &lt;...

     

    这篇博客是在 Spring boot 入门教程-全局异常处理及日志输出  的基础上完成的。

    我们在做项目时比如商城项目,有的页面的打开是需要登陆的而有的页面则不需要,所以这里就需要一种验证是否登录,或者登录是否过期,这里说一种token令牌+拦截器的方式。

    生成token 使用JWT。

    1.引入

            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.0</version>
            </dependency>

    2.生成token令牌的工具类

    public class TokenUtils {
    
        /**
         * 签名秘钥
         */
        public static final String SECRET = "admin";
    
        /**
         * 生成token
         *
         * @param id 一般传入userName
         * @return
         */
        public static String createJwtToken(String id) {
            String issuer = "www.xxxx.com";
            String subject = "xxxx@126.com";
            long ttlMillis = 3600000;
            return createJwtToken(id, issuer, subject, ttlMillis);
        }
    
        /**
         * 生成Token
         *
         * @param id        编号
         * @param issuer    该JWT的签发者,是否使用是可选的
         * @param subject   该JWT所面向的用户,是否使用是可选的;
         * @param ttlMillis 签发时间 (有效时间,过期会报错)
         * @return token String
         */
        public static String createJwtToken(String id, String issuer, String subject, long ttlMillis) {
    
            // 签名算法 ,将对token进行签名
            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    
            // 生成签发时间
            long nowMillis = System.currentTimeMillis();
            Date now = new Date(nowMillis);
    
            // 通过秘钥签名JWT
            byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET);
            Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
    
            // Let's set the JWT Claims
            JwtBuilder builder = Jwts.builder().setId(id)
                    .setIssuedAt(now)
                    .setSubject(subject)
                    .setIssuer(issuer)
                    .signWith(signatureAlgorithm, signingKey);
    
            // if it has been specified, let's add the expiration
            if (ttlMillis >= 0) {
                long expMillis = nowMillis + ttlMillis;
                Date exp = new Date(expMillis);
                builder.setExpiration(exp);
            }
    
            // Builds the JWT and serializes it to a compact, URL-safe string
            return builder.compact();
    
        }
    
        // Sample method to validate and read the JWT
        public static Claims parseJWT(String jwt) {
            // This line will throw an exception if it is not a signed JWS (as expected)
            Claims claims = Jwts.parser()
                    .setSigningKey(DatatypeConverter.parseBase64Binary(SECRET))
                    .parseClaimsJws(jwt).getBody();
            return claims;
        }
    
        public static void main(String[] args) {
            System.out.println(TokenUtils.createJwtToken("11111"));
        }
    }

    3.注入当前登录用户注解

    /**
     * @BelongsProject: JDTaste
     * @BelongsPackage: com.jdtaste.common.util
     * @Author: 
     * @CreateTime: 2018-07-04 15:39
     * @Description: 在Controller的方法参数中使用此注解,该方法在映射时会注入当前登录的User对象
     */
    @Target(ElementType.PARAMETER)          // 可用在方法的参数上
    @Retention(RetentionPolicy.RUNTIME)     // 运行时有效
    public @interface CurrentUser {
    }
    

    4.需要登录的标记注解

    /**
     * @BelongsProject: JDTaste
     * @BelongsPackage: com.jdtaste.common.util
     * @Author: 
     * @CreateTime: 2018-07-04 15:38
     * @Description: 在需要登录验证的Controller的方法上使用此注解
     */
    @Target({ElementType.METHOD})// 可用在方法名上
    @Retention(RetentionPolicy.RUNTIME)// 运行时有效
    public @interface LoginRequired {
    }

    5.编辑拦截器

    /**
     * @BelongsProject: 
     * @BelongsPackage: com.jdtaste.jdtastesso.web.intercepter
     * @Author: 
     * @CreateTime: 2018-07-04 09:50
     * @Description: 拦截器
     */
    public class AuthenticationInterceptor implements HandlerInterceptor {
        public final static String ACCESS_TOKEN = "accessToken";
        @Resource
        IUserBaseService userBaseService;
    
        // 在业务处理器处理请求之前被调用
        @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();
            // 判断接口是否需要登录
            LoginRequired methodAnnotation = method.getAnnotation(LoginRequired.class);
    
            // 有 @LoginRequired 注解,需要认证
            if (methodAnnotation != null) {
                // 判断是否存在令牌信息,如果存在,则允许登录
                String accessToken = request.getHeader("Authorization");
    
    
                if (null == accessToken) {
                    throw new CommonException(401, "无token,请重新登录");
                } else {
                    // 从Redis 中查看 token 是否过期
                    Claims claims;
                    try{
                         claims = TokenUtils.parseJWT(accessToken);
                    }catch (ExpiredJwtException e){
                        response.setStatus(401);
                        throw new CommonException(401, "token失效,请重新登录");
                    }catch (SignatureException se){
                        response.setStatus(401);
                        throw new CommonException(401, "token令牌错误");
                    }
    
                    String userName = claims.getId();
                    UserBase user = userBaseService.findUserByAccount(userName);
                    if (user == null) {
                        response.setStatus(401);
                        throw new CommonException(401, "用户不存在,请重新登录");
                    }
                    // 当前登录用户@CurrentUser
                    request.setAttribute(CurrentUserConstants.CURRENT_USER, user);
                    return true;
                }
    
            } else {//不需要登录可请求
                return true;
            }
        }
        // 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
        @Override
        public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    
        }
    
        // 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
        @Override
        public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        }
    }

     

    /**
     * @BelongsProject: 
     * @BelongsPackage: com.jdtaste.jdtastesso.web.intercepter.auth
     * @Author: 
     * @CreateTime: 2018-07-04 15:45
     * @Description: 当前用户
     */
    public class CurrentUserConstants {
        /**
         * 当前用户参数名
         */
        public final static String CURRENT_USER = "CurrentUser";
    }
    

     

    6.自定义参数解析器

    /**
     *  
     * @BelongsPackage: com.jdtaste.jdtastesso.web.intercepter.auth
     * @Author: 
     * @CreateTime: 2018-07-04 15:42
     * @Description: 自定义参数解析器
     * 增加方法注入,将含有 @CurrentUser 注解的方法参数注入当前登录用户
     */
    public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
        /*
         * supportsParameter:用于判定是否需要处理该参数分解,返回true为需要,并会去调用下面的方法resolveArgument。
         *resolveArgument:真正用于处理参数分解的方法,返回的Object就是controller方法上的形参对象。
         *
         * */
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            System.out.println("----------supportsParameter-----------" + parameter.getParameterType());
            return parameter.getParameterType().isAssignableFrom(UserBase.class)//判断是否能转成UserBase 类型
                    && parameter.hasParameterAnnotation(CurrentUser.class);//是否有CurrentUser注解
        }
    
        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
            System.out.println("--------------resolveArgument-------------" + parameter);
            UserBase user = (UserBase) webRequest.getAttribute(CurrentUserConstants.CURRENT_USER, RequestAttributes.SCOPE_REQUEST);
            if (user != null) {
                return user;
            }
            throw new MissingServletRequestPartException(CurrentUserConstants.CURRENT_USER);
        }
    }

    7.将拦截器和参数解析器加入容器

    /**
     * @BelongsProject: 
     * @BelongsPackage: com.jdtaste.jdtastesso.conf
     * @Author: 
     * @CreateTime: 2018-07-04 10:03
     * @Description: 配置URLInterceptor拦截器,以及拦截路径
     */
    @EnableWebMvc
    @Configuration
    public class MyWebAppConfigurer extends WebMvcConfigurerAdapter {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // addPathPatterns 用于添加拦截规则
            // excludePathPatterns 用户排除拦截
            registry.addInterceptor(authenticationInterceptor())
                    .addPathPatterns("/*/*");
            super.addInterceptors(registry);
        }
    
        @Override
        public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
            argumentResolvers.add(currentUserMethodArgumentResolver());
            super.addArgumentResolvers(argumentResolvers);
        }
    
        @Bean
        public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() {
            return new CurrentUserMethodArgumentResolver();
        }
    
        /**
         * 解决 拦截器中注入bean 失败情况出现
         * addArgumentResolvers方法中 添加
         *  argumentResolvers.add(currentUserMethodArgumentResolver());
         */
        @Bean
        public AuthenticationInterceptor authenticationInterceptor() {
            return new AuthenticationInterceptor();
        }
    }
     argumentResolvers.add(currentUserMethodArgumentResolver());
            super.addArgumentResolvers(argumentResolvers);
        }
    
        @Bean
        public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() {
            return new CurrentUserMethodArgumentResolver();
        }
    
        /**
         * 解决 拦截器中注入bean 失败情况出现
         * addArgumentResolvers方法中 添加
         *  argumentResolvers.add(currentUserMethodArgumentResolver());
         */
        @Bean
        public AuthenticationInterceptor authenticationInterceptor() {
            return new AuthenticationInterceptor();
        }
    }

    8.Controller

        @LoginRequired
        @RequestMapping(value = "/token")
        public String token(@CurrentUser UserBase userBase,String account,String token) {
    
            log.info(account+"----"+token);
            log.info("----"+userBase.getAccount());
            log.info("params==" + userBase.toString());
            if (userBaseService.findUserByAccount(userBase.getAccount()) == null) {
                return "账号不存在";
            } else {
                UserBase result = null;
                result = userBaseService.login(userBase);
                //生成token
                //String accessToken=TokenUtils.createJwtToken(userBase.getAccount());
                if (result == null) {
                    return  "密码错误";
                } else {
    
                    return "SUCCESS";
                }
            }
        }
    @LoginRequired
        @RequestMapping(value = "/token")
        public String token(@CurrentUser UserBase userBase,String account,String token) {
    
            log.info(account+"----"+token);
            log.info("----"+userBase.getAccount());
            log.info("params==" + userBase.toString());
            if (userBaseService.findUserByAccount(userBase.getAccount()) == null) {
                return "账号不存在";
            } else {
                UserBase result = null;
                result = userBaseService.login(userBase);
                //生成token
                //String accessToken=TokenUtils.createJwtToken(userBase.getAccount());
                if (result == null) {
                    return  "密码错误";
                } else {
    
                    return "SUCCESS";
                }
            }
        }

     

    9.前端发送请求 (axios)

    _this.$http.post('/api/user/login',
              this.login,
              {
                headers: {
                  Authorization: JSON.parse(sessionStorage.getItem("loginUser")).accessToken,
                }
              }).then((res) => {
              console.log(res);
            });

    10 .请求过程 

    拦截器-->参数解析器-->controler-->拦截器

    注意:参数解析器当第一个返回true 才执行第二个方法

    展开全文
  • 手把手教你实现JWT Token

    千次阅读 2019-10-26 09:31:08
    Json Web Token(JWT) 近几年是前后端分离常用的Token技术,是目前最流行的跨域身份验证解决方案。你可以通过文章JWT。今天我们来手写一个通用的JWT服务。DEMO获取方式在文末,实现在jwt相关包下 2. spring-...

    640?wx_fmt=gif

    1. 前言

    Json Web Token (JWT) 近几年是前后端分离常用的 Token 技术,是目前最流行的跨域身份验证解决方案。你可以通过文章 JWT。今天我们来手写一个通用的 JWT 服务。DEMO 获取方式在文末,实现在 jwt 相关包下

    2. spring-security-jwt

    spring-security-jwt 是 Spring Security Crypto 提供的 JWT 工具包 。

      <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>${spring-security-jwt.version}</version>
      </dependency>
    

    核心类只有一个: org.springframework.security.jwt.JwtHelper 。它提供了两个非常有用的静态方法。

    3. JWT 编码

    JwtHelper 提供的第一个静态方法就是 encode(CharSequence content, Signer signer) 这个是用来生成jwt的方法 需要指定 payload 跟 signer 签名算法。payload 存放了一些可用的不敏感信息:

    • iss jwt签发者

    • sub jwt所面向的用户

    • aud 接收jwt的一方

    • iat jwt的签发时间

    • exp jwt的过期时间,这个过期时间必须要大于签发时间 iat

    • jti jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

    除了以上提供的基本信息外,我们可以定义一些我们需要传递的信息,比如目标用户的权限集 等等。切记不要传递密码等敏感信息 ,因为 JWT 的前两段都是用了 BASE64 编码,几乎算是明文了。

    3.1 构建 JWT 中的 payload

    我们先来构建 payload :

     /**
      * 构建 jwt payload
      *
      * @author Felordcn
      * @since 11:27 2019/10/25
      **/
     public class JwtPayloadBuilder {
    
         private Map<String, String> payload = new HashMap<>();
         /**
          * 附加的属性
          */
         private Map<String, String> additional;
         /**
          * jwt签发者
          **/
         private String iss;
         /**
          * jwt所面向的用户
          **/
         private String sub;
         /**
          * 接收jwt的一方
          **/
         private String aud;
         /**
          * jwt的过期时间,这个过期时间必须要大于签发时间
          **/
         private LocalDateTime exp;
         /**
          * jwt的签发时间
          **/
         private LocalDateTime iat = LocalDateTime.now();
         /**
          * 权限集
          */
         private Set<String> roles = new HashSet<>();
         /**
          * jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
          **/
         private String jti = IdUtil.simpleUUID();
    
         public JwtPayloadBuilder iss(String iss) {
             this.iss = iss;
             return this;
         }
    
    
         public JwtPayloadBuilder sub(String sub) {
             this.sub = sub;
             return this;
         }
    
         public JwtPayloadBuilder aud(String aud) {
             this.aud = aud;
             return this;
         }
    
    
         public JwtPayloadBuilder roles(Set<String> roles) {
             this.roles = roles;
             return this;
         }
    
         public JwtPayloadBuilder expDays(int days) {
             Assert.isTrue(days > 0, "jwt expireDate must after now");
             this.exp = this.iat.plusDays(days);
             return this;
         }
    
         public JwtPayloadBuilder additional(Map<String, String> additional) {
             this.additional = additional;
             return this;
         }
    
         public String builder() {
             payload.put("iss", this.iss);
             payload.put("sub", this.sub);
             payload.put("aud", this.aud);
             payload.put("exp", this.exp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
             payload.put("iat", this.iat.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
             payload.put("jti", this.jti);
    
             if (!CollectionUtils.isEmpty(additional)) {
                 payload.putAll(additional);
             }
             payload.put("roles", JSONUtil.toJsonStr(this.roles));
             return JSONUtil.toJsonStr(JSONUtil.parse(payload));
    
         }
    
     }
    

    通过建造类 JwtClaimsBuilder 我们可以很方便来构建 JWT 所需要的 payload json 字符串传递给 encode(CharSequence content, Signer signer) 中的 content 。

    3.2 生成 RSA 密钥并进行签名

    为了生成 JWT Token 我们还需要使用 RSA 算法来进行签名。这里我们使用 JDK 提供的证书管理工具 Keytool 来生成 RSA 证书 ,格式为 jks 格式。

    生成证书命令参考:

    keytool -genkey -alias felordcn -keypass felordcn -keyalg RSA -storetype PKCS12 -keysize 1024 -validity 365 -keystore d:/keystores/felordcn.jks -storepass 123456 -dname "CN=(Felord), OU=(felordcn), O=(felordcn), L=(zz), ST=(hn), C=(cn)"

     
    其中  -alias felordcn -storepass 123456  我们要作为配置使用要记下来。我们要使用下面定义的这个类来读取证书

     
     package cn.felord.spring.security.jwt;
    
     import org.springframework.core.io.ClassPathResource;
    
     import java.security.KeyFactory;
     import java.security.KeyPair;
     import java.security.KeyStore;
     import java.security.PublicKey;
     import java.security.interfaces.RSAPrivateCrtKey;
     import java.security.spec.RSAPublicKeySpec;
    
     /**
      * KeyPairFactory
      *
      * @author Felordcn
      * @since 13:41 2019/10/25
      **/
     class KeyPairFactory {
    
         private KeyStore store;
    
         private final Object lock = new Object();
    
         /**
          * 获取公私钥.
          *
          * @param keyPath  jks 文件在 resources 下的classpath
          * @param keyAlias  keytool 生成的 -alias 值  felordcn
          * @param keyPass  keytool 生成的  -storepass 值  123456
          * @return the key pair 公私钥对
          */
        KeyPair create(String keyPath, String keyAlias, String keyPass) {
             ClassPathResource resource = new ClassPathResource(keyPath);
             char[] pem = keyPass.toCharArray();
             try {
                 synchronized (lock) {
                     if (store == null) {
                         synchronized (lock) {
                             store = KeyStore.getInstance("jks");
                             store.load(resource.getInputStream(), pem);
                         }
                     }
                 }
                 RSAPrivateCrtKey key = (RSAPrivateCrtKey) store.getKey(keyAlias, pem);
                 RSAPublicKeySpec spec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent());
                 PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(spec);
                 return new KeyPair(publicKey, key);
             } catch (Exception e) {
                 throw new IllegalStateException("Cannot load keys from store: " + resource, e);
             }
    
         }
     }
    

    获取了 KeyPair 就能获取公私钥 生成 Jwt 的两个要素就完成了。我们可以和之前定义的 JwtPayloadBuilder 一起封装出生成 Jwt Token 的方法:

         private String jwtToken(String aud, int exp, Set<String> roles, Map<String, String> additional) {
             String payload = jwtPayloadBuilder
                     .iss(jwtProperties.getIss())
                     .sub(jwtProperties.getSub())
                     .aud(aud)
                     .additional(additional)
                     .roles(roles)
                     .expDays(exp)
                     .builder();
             RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    
             RsaSigner signer = new RsaSigner(privateKey);
             return JwtHelper.encode(payload, signer).getEncoded();
         }
    

    通常情况下 Jwt Token 都是成对出现的,一个为平常请求携带的 accessToken, 另一个只作为刷新 accessToken 之用的 refreshToken 。而且 refreshToken 的过期时间要相对长一些。当 accessToken 失效而refreshToken 有效时,我们可以通过 refreshToken 来获取新的 Jwt Token对 ;当两个都失效就用户就必须重新登录了。

    生成 Jwt Token对 的方法如下:

         public JwtTokenPair jwtTokenPair(String aud, Set<String> roles, Map<String, String> additional) {
             String accessToken = jwtToken(aud, jwtProperties.getAccessExpDays(), roles, additional);
             String refreshToken = jwtToken(aud, jwtProperties.getRefreshExpDays(), roles, additional);
    
             JwtTokenPair jwtTokenPair = new JwtTokenPair();
             jwtTokenPair.setAccessToken(accessToken);
             jwtTokenPair.setRefreshToken(refreshToken);
             // 放入缓存
             jwtTokenStorage.put(jwtTokenPair, aud);
             return jwtTokenPair;
         }
    

    通常 Jwt Token对 会在返回给前台的同时放入缓存中。过期策略你可以选择分开处理,也可以选择以refreshToken 的过期时间为准。

    4. JWT 解码以及验证

    JwtHelper 提供的第二个静态方法是Jwt decodeAndVerify(String token, SignatureVerifier verifier) 用来 验证和解码 Jwt Token 。我们获取到请求中的token后会解析出用户的一些信息。通过这些信息去缓存中对应的token ,然后比对并验证是否有效(包括是否过期)。

          /**
           * 解码 并校验签名 过期不予解析
           *
           * @param jwtToken the jwt token
           * @return the jwt claims
           */
          public JSONObject decodeAndVerify(String jwtToken) {
              Assert.hasText(jwtToken, "jwt token must not be bank");
              RSAPublicKey rsaPublicKey = (RSAPublicKey) this.keyPair.getPublic();
              SignatureVerifier rsaVerifier = new RsaVerifier(rsaPublicKey);
              Jwt jwt = JwtHelper.decodeAndVerify(jwtToken, rsaVerifier);
              String claims = jwt.getClaims();
              JSONObject jsonObject = JSONUtil.parseObj(claims);
              String exp = jsonObject.getStr(JWT_EXP_KEY);
             // 是否过期
              if (isExpired(exp)) {
                  throw new IllegalStateException("jwt token is expired");
              }
              return jsonObject;
          }
    

    上面我们将有效的 Jwt Token 中的 payload 解析为 JSON对象 ,方便后续的操作。

    5. 配置

    我们将 JWT 的可配置项抽出来放入 JwtProperties 如下:

     /**
      * Jwt 在 springboot application.yml 中的配置文件
      *
      * @author Felordcn
      * @since 15 :06 2019/10/25
      */
     @Data
     @ConfigurationProperties(prefix=JWT_PREFIX)
     public class JwtProperties {
         static final String JWT_PREFIX= "jwt.config";
         /**
          * 是否可用
          */
         private boolean enabled;
         /**
          * jks 路径
          */
         private String keyLocation;
         /**
          * key alias
          */
         private String keyAlias;
         /**
          * key store pass
          */
         private String keyPass;
         /**
          * jwt签发者
          **/
         private String iss;
         /**
          * jwt所面向的用户
          **/
         private String sub;
         /**
          * access jwt token 有效天数
          */
         private int accessExpDays;
         /**
          * refresh jwt token 有效天数
          */
         private int refreshExpDays;
     }
    

    然后我们就可以配置 JWT 的 javaConfig 如下:

     /**
      * JwtConfiguration
      *
      * @author Felordcn
      * @since 16 :54 2019/10/25
      */
     @EnableConfigurationProperties(JwtProperties.class)
     @ConditionalOnProperty(prefix = "jwt.config",name = "enabled")
     @Configuration
     public class JwtConfiguration {
    
    
         /**
          * Jwt token storage .
          *
          * @return the jwt token storage
          */
         @Bean
         public JwtTokenStorage jwtTokenStorage() {
             return new JwtTokenCacheStorage();
         }
    
    
         /**
          * Jwt token generator.
          *
          * @param jwtTokenStorage the jwt token storage
          * @param jwtProperties   the jwt properties
          * @return the jwt token generator
          */
         @Bean
         public JwtTokenGenerator jwtTokenGenerator(JwtTokenStorage jwtTokenStorage, JwtProperties jwtProperties) {
             return new JwtTokenGenerator(jwtTokenStorage, jwtProperties);
         }
    
     }
    

    然后你就可以通过 JwtTokenGenerator 编码/解码验证 Jwt Token 对 ,通过 JwtTokenStorage 来处理 Jwt Token 缓存。缓存这里我用了Spring Cache Ehcache 来实现,你也可以切换到 Redis 。相关单元测试参见 DEMO

    6. 总结

    今天我们利用 spring-security-jwt 手写了一套 JWT 逻辑。无论对你后续结合 Spring Security 还是 Shiro 都十分有借鉴意义。下一篇我们会讲解 JWT 结合Spring Security ,敬请关注公众号:Felordcn 来及时获取资料。

    本次的 DEMO 可通过关注公众号回复 day05 获取。

     

    640?wx_fmt=png

    640?wx_fmt=gif

    展开全文
  • Syntax error on token ""\',\'"", delete this token 75: Class.forName(dri); 76: con =DriverManager.getConnection(conStr,"test_db","test_pw"); 77: stmt = con.createStatement(); 78: String sql=...
  • golang 中使用 JWT 实现登录验证

    千次阅读 2020-01-12 01:01:04
    JWT是json web token 具体jwt的组成,加密方式等等自行百度解决,我这里仅写实现案例: 控制器代码 package controller import ( "errors" "fmt" "gindemo/dto" "gindemo/middleware" "gindemo/middleware/jwt" ...
  • 1 JWT标准规范JWT(JSON Web Token)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。一个JWT由三部分组成,头部、载荷与签名。头部:用于说明签名的加密算法等,下面类型的...
  • 前后端分离下的token机制

    千次阅读 2019-06-01 19:38:51
    一、 什么是token ? Token 是在服务端产生的,如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。 二、token...
  • String str= "{ \" tagJson \" :[{ \" type \" : \" people \" , \" tagName \" :[ \" 是的 \" , \" 是的 \" , \" 大多数 \" ]},{ \" type \" : \" ADDR \" , \" tagName \" :[ \" 俄文 \" , \" 嗯嗯 \" , \" 的索科...
  • 这篇文章将详细讲解一道CSS注入题目,包括CSS注入、越权、csrf-token窃取及CSP绕过,同时总结XSS绕过知识,希望对您有所帮助。第一次参加CTF,还是学到了很多东西。人生路上,要珍惜好每一天与家人陪伴的日子。感谢...
  • 前端给后端传递json数组时,后端的形参名要与前端的对应。 后端形参 前端形参
  • Web项目中经常会用token来进行用户的访问验证,那么在获得token之后,如果有很多地方需要根据token获得对应的用户信息,你会怎么获取? 本文给大家提供N种方式,对照一下,看看你的项目中所使用的方式属于哪个Level...
  • 淘宝Refrash_token签名错误的解决办法

    千次阅读 2013-02-19 15:19:48
    最近在做淘宝相关应用,想要通过Refrash_token来延长SessionKey的授权时间,但是总是报406 sign error. 经过多次尝试和多方询问,方才知道原来淘宝给的.net SDK里面的签名方法... private string GetRef
  • Go实战--golang中使用JWT(JSON Web Token)

    万次阅读 多人点赞 2019-07-18 19:33:38
    实战–go中使用cookie今天就来跟大家简单介绍一下golang中如何使用token,当然是要依赖一下github上的优秀的开源库了。首先,要搞明白一个问题,token、cookie、session的区别。token、cookie、session的区别Cookie ...
  • 有个地方需要注意: 我这里刷新产生新的refreshToken时 旧的refreshToken并没有失效,如果不是特别敏感这点的话可以不计较,若是在意的话,那需要自己处理:比如用缓存记录失效的token每次token认证判断是否是失效的...
  • springCloud gateway+jwt方式做token校验

    万次阅读 热门讨论 2019-08-17 16:25:17
    项目中需要做接口的token校验,项目api是作为eureka-client去调用服务的,所有的以api开头请求过来都需要进行校验是否有token并校验其正确性。 1.pom文件: <?xml version="1.0" encoding="UTF-8"?> <...
  • Spring Cloud Gateway 实现Token校验

    千次阅读 2020-07-16 17:53:14
    SpringCloud视频教程: ...from_uin=171851697&...http://www.51ufo.cn/%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E5%88%86%E5%B8%83%E5%BC%8F/2020/07/16/Spring-Cloud-Gateway-%E5%AE%9E%E7%8E%B0Token%E6%A0%A
  • Spring Security Jwt Token 自动刷新

    千次阅读 2020-06-07 17:38:32
    最近项目中有这么一个功能,用户登录系统后,需要给 用户 颁发一个 token ,后续访问系统的请求都需要带上这个 token ,如果请求没有带上这个 token 或者 token 过期了,那么禁止访问系统。如果用户一直访问系统,那么...
  • JAVA——token理解

    千次阅读 2018-09-30 11:47:27
    Token(令牌)机制 防止因为后退或者刷新来重复提交表单内容的Token机制 Token,就是Token(令牌)机制,最大的特点就是随机性,不可预测。一般黑客或软件无法猜测出来。 实现原理:一致性。 jsp生成表单时,在表单中...
  • 使用token进行用户身份验证

    千次阅读 2019-08-05 23:53:17
    使用token进行用户身份验证 参考文章:https://blog.csdn.net/u014799292/article/details/88365086 写的很详细,具体讲解了token的原理. 为什么使用token 以前都是使用session: 当用户第一次通过浏览器使用...
  • redis + jwt实现token认证

    千次阅读 2019-09-16 22:13:02
    登录授权获取token2.登出使token失效3.续期token4.获取新的token 业务场景 在前后端分离的场景下,越来越多的项目使用token作为接口的安全机制,APP或者web端使用token与后端接口交互,以达到安全的目的 拦截器 ...
  • 后续每次请求都会将此token放在请求头中传递到后端服务,后端服务会有一个过滤器对token进行拦截校验,校验token的合法性以及token是否过期,如果token过期则会让前端跳转到登录页面重新登录。 因为token中一般会...
  • API关于TOKEN的使用

    千次阅读 2019-01-08 19:52:37
    public static Map<String, Object> valid(String token) throws ParseException, JOSEException { // 解析token JWSObject jwsObject = JWSObject.parse(token); // 获取到载荷 Payload payload = ...
  • private static ConcurrentHashMap<String, UserToken> map = new ConcurrentHashMap<String, UserToken>(); /** * 生成加密Token * * @param username * @return */ public static UserToken ...
  • Token

    千次阅读 2018-11-21 10:12:05
    1.Token是什么 Token是一个字符串,是一段根据特殊规则生成的唯一的、保存在缓存服务器(如Redis)中的key,这个key对应的value是用户账户数据。每个用户登录后,服务器生成一个类似Token并缓存,之后用户每次请求...
  • jwt+shiro+redis实现token的自动刷新和token的可控性

    千次阅读 多人点赞 2020-08-17 23:21:29
    文章目录一、为何要使用jwt+shiro+redis二、AccessToken和RefreshToken2-1. Shiro + JWT实现无状态鉴权机制2-2. 关于AccessToken及RefreshToken概念说明2-3. 关于Redis中保存RefreshToken信息(做到JWT的可控性)2-4. ...
  • 基于SpringBoot使用JWT实现Token认证

    万次阅读 热门讨论 2019-03-11 08:59:32
    2、、基于token的鉴权机制 3、JWT的构成 二、简单实战 1、新建一个简单的springboot项目 2、自定义登录异常处理 3、全局异常拦截处理输出 4、创建工具类 5、token的生成和拦截 三、参考文献 一、JWT的介绍 ...
  • Java令牌Token登录与退出

    千次阅读 2020-05-25 20:34:15
    Java令牌Token登录与退出 Token 之前的博客已经介绍了各种登录的方式,现在直接介绍一种现在比较流行的登录方式,无状态登录,只需要客户端携带令牌就能登陆,服务器不再存储登录状态。突然粉丝量爆棚,开心死了,...
  • springboot之token的用法总结

    万次阅读 2018-04-03 11:50:24
     header:头部信息主要包括(参数的类型--JWT,签名的算法--HS256) payload:存放自己想要的信息 sign:是为了防止恶意篡改数据二、JWT的生成和解析生成:String token=JavaWebTokenUtil.createJWT...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 265,870
精华内容 106,348
关键字:

tokenstring