java - JWT 实际上是如何与 Spring MVC 一起创建 token 和验证 token 的?

标签 java spring-mvc spring-security jwt

我实际上希望对 JWT 概念以及它如何与 Spring MVC 一起工作有更多更清晰的理解。我找到链接 https://github.com/nielsutrecht/jwt-angular-spring ,其程序运行良好。 但我想知道它是如何创建 token 以及用户如何登录应用程序的。我需要一些说明/回答它是如何工作的?请做有需要的。

我的理解:在我看来,当您启动应用程序时,将调用 GenericFilterBean 实现类,它将生成 JWT token 并将其发送到本地存储中的 UI(虽然不确定),然后该 token 将出现在 header 中有请求,然后它将得到验证并授予用户访问权限?

我想放一些代码片段供引用(即使你可以从提到的链接中查找代码)

AuthenticationTokenProcessingFilter.java

public class AuthenticationTokenProcessingFilter extends GenericFilterBean {

    private final UserDetailsService userService;

    public AuthenticationTokenProcessingFilter(UserDetailsService userService){
        this.userService = userService;
    }


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,ServletException{
        HttpServletRequest httpRequest = this.getAsHttpRequest(request);

        String authToken = this.extractAuthTokenFromRequest(httpRequest);
        String userName = TokenUtils.getUserNameFromToken(authToken);

        if (userName != null) {

            UserDetails userDetails = this.userService.loadUserByUsername(userName);

            if (TokenUtils.validateToken(authToken, userDetails)) {

                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        chain.doFilter(request, response);
    }


    private HttpServletRequest getAsHttpRequest(ServletRequest request){
        if (!(request instanceof HttpServletRequest)) {
            throw new RuntimeException("Expecting an HTTP request");
        }

        return (HttpServletRequest) request;
    }


    private String extractAuthTokenFromRequest(HttpServletRequest httpRequest){
        /* Get token from header */
        String authToken = httpRequest.getHeader("X-Auth-Token");
        System.out.println("AUTH TOKEN : "+authToken);

        /* If token not found get it from request parameter */
        if (authToken == null) {
            authToken = httpRequest.getParameter("token");
        }
        return authToken;
    }
}

TokenUtils.java

public class TokenUtils{
    public static final String MAGIC_KEY = "obfuscate";

    public static String createToken(UserDetails userDetails){
        System.out.println(" ----- Create Token ------");
        /* Expires in one hour */
        long expires = System.currentTimeMillis() + 1000L * 60 * 60;

        StringBuilder tokenBuilder = new StringBuilder();
        tokenBuilder.append(userDetails.getUsername());
        tokenBuilder.append(":");
        tokenBuilder.append(expires);
        tokenBuilder.append(":");
        tokenBuilder.append(TokenUtils.computeSignature(userDetails, expires));

        return tokenBuilder.toString();
    }


    public static String computeSignature(UserDetails userDetails, long expires){
        System.out.println("------ Compute Signature ------");
        StringBuilder signatureBuilder = new StringBuilder();
        signatureBuilder.append(userDetails.getUsername());
        signatureBuilder.append(":");
        signatureBuilder.append(expires);
        signatureBuilder.append(":");
        signatureBuilder.append(userDetails.getPassword());
        signatureBuilder.append(":");
        signatureBuilder.append(TokenUtils.MAGIC_KEY);

        MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("No MD5 algorithm available!");
        }
        System.out.println(new String(Hex.encode(digest.digest(signatureBuilder.toString().getBytes()))));
        return new String(Hex.encode(digest.digest(signatureBuilder.toString().getBytes())));
    }


    public static String getUserNameFromToken(String authToken){
        System.out.println("----- Get Username From TOken ----");
        if (null == authToken) {
            return null;
        }

        String[] parts = authToken.split(":");
        return parts[0];
    }


    public static boolean validateToken(String authToken, UserDetails userDetails)  {
        System.out.println("=== Validate Token ===");
        String[] parts = authToken.split(":");
        long expires = Long.parseLong(parts[1]);
        String signature = parts[2];

        if (expires < System.currentTimeMillis()) {
            return false;
        }
        System.out.println(signature.equals(TokenUtils.computeSignature(userDetails, expires)));
        return signature.equals(TokenUtils.computeSignature(userDetails, expires));
    }
}

上下文.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">

    <context:annotation-config />

    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:database.properties</value>
            </list>
        </property>
    </bean>

    <!-- MySQL DataSource -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="username" value="${jdbc.user}" />
        <property name="password" value="${jdbc.pass}" />
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
    </bean>

    <!-- Entity Manager Factory -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="persistenceUnitName" value="examplePU" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="generateDdl" value="true" />
                <property name="showSql" value="true" />
            </bean>
        </property>
    </bean>

    <!-- Transaction Manager -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>


    <bean id="newsEntryDao" class="net.dontdrinkandroot.dao.newsentry.JpaNewsEntryDao" />

    <bean id="userDao" class="net.dontdrinkandroot.dao.user.JpaUserDao" />

    <bean id="dataBaseInitializer" class="net.dontdrinkandroot.dao.DataBaseInitializer" init-method="initDataBase">
        <constructor-arg ref="userDao" />
        <constructor-arg ref="newsEntryDao" />
        <constructor-arg ref="passwordEncoder" />
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager" />

    <!-- =======================
        INIT REST COMPONENTS 
         ======================= -->

    <context:component-scan base-package="net.dontdrinkandroot.resources" />

    <bean id="objectMapper" class="org.codehaus.jackson.map.ObjectMapper" />



    <!-- ====================================== 
        SPRING SECURITY SETUP
         ====================================== -->

    <bean id="passwordEncoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder">
        <constructor-arg value="ThisIsASecretSoChangeMe" />
    </bean>

    <security:authentication-manager id="authenticationManager">
        <security:authentication-provider user-service-ref="userDao">
            <security:password-encoder ref="passwordEncoder"></security:password-encoder>
        </security:authentication-provider>
    </security:authentication-manager>

    <security:http
            realm="Protected API"
            use-expressions="true"
            auto-config="false"
            create-session="stateless"
            entry-point-ref="unauthorizedEntryPoint"
            authentication-manager-ref="authenticationManager">
        <security:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
        <security:intercept-url pattern="/rest/user/authenticate" access="permitAll" />
        <security:intercept-url method="GET" pattern="/rest/news/**" access="hasRole('user')" />
        <security:intercept-url method="PUT" pattern="/rest/news/**" access="hasRole('admin')" />
        <security:intercept-url method="POST" pattern="/rest/news/**" access="hasRole('admin')" />
        <security:intercept-url method="DELETE" pattern="/rest/news/**" access="hasRole('admin')" />
    </security:http>

    <bean id="unauthorizedEntryPoint" class="net.dontdrinkandroot.rest.UnauthorizedEntryPoint" />

    <bean id="authenticationTokenProcessingFilter" class="net.dontdrinkandroot.rest.AuthenticationTokenProcessingFilter" >
        <constructor-arg ref="userDao" />
    </bean>

</beans>

编辑-1:

AUTH TOKEN : null
----- Get Username From Token ----
Jan 01, 2016 2:35:01 AM com.sun.jersey.spi.container.servlet.WebComponent filterFormParameters
WARNING: A servlet request, to the URI http://localhost:8080/angular-rest-security/rest/user/authenticate, contains form parameters in the request body but the request body has been consumed by the servlet or a servlet filter accessing the request parameters. Only resource methods using @FormParam will work as expected. Resource methods consuming the request body by other means will not work as expected.
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
 ----- Create Token ------
------ Compute Signature ------
525b8e635bb234684d2a02b99f38d687
AUTH TOKEN : null
----- Get Username From Token ----
Jan 01, 2016 2:36:27 AM com.sun.jersey.spi.container.servlet.WebComponent filterFormParameters
WARNING: A servlet request, to the URI http://localhost:8080/angular-rest-security/rest/user/authenticate, contains form parameters in the request body but the request body has been consumed by the servlet or a servlet filter accessing the request parameters. Only resource methods using @FormParam will work as expected. Resource methods consuming the request body by other means will not work as expected.
AUTH TOKEN : admin:1451599569652:525b8e635bb234684d2a02b99f38d687
----- Get Username From Token ----
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
=== Validate Token ===
------ Compute Signature ------
525b8e635bb234684d2a02b99f38d687
true
------ Compute Signature ------
525b8e635bb234684d2a02b99f38d687
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
 ----- Create Token ------
------ Compute Signature ------
b6238344022f3f4dd3787f0f8fa99b44
AUTH TOKEN : null
----- Get Username From Token ----
AUTH TOKEN : admin:1451599596826:b6238344022f3f4dd3787f0f8fa99b44
----- Get Username From Token ----
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
=== Validate Token ===
------ Compute Signature ------
b6238344022f3f4dd3787f0f8fa99b44
true
------ Compute Signature ------
b6238344022f3f4dd3787f0f8fa99b44
AUTH TOKEN : admin:1451599596826:b6238344022f3f4dd3787f0f8fa99b44
----- Get Username From Token ----
Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.name=?
Hibernate: select roles0_.User_id as User_id1_1_0_, roles0_.roles as roles2_2_0_ from User_roles roles0_ where roles0_.User_id=?
=== Validate Token ===
------ Compute Signature ------
b6238344022f3f4dd3787f0f8fa99b44
true
------ Compute Signature ------
b6238344022f3f4dd3787f0f8fa99b44
02:36:38,728 INFO NewsEntryResource.list():49 - list()
Hibernate: select newsentry0_.id as id1_0_, newsentry0_.content as content2_0_, newsentry0_.date as date3_0_ from NewsEntry newsentry0_ order by newsentry0_.date desc
AUTH TOKEN : null
----- Get Username From Token ----

最佳答案

我是您提供的链接 ( https://github.com/nielsutrecht/jwt-angular-spring) 的作者。

示例应用程序使用 Jjwt 库来创建和解密 JSON 网络 token 。在我的示例应用程序中, token 是在一个人设法成功登录时创建的。这发生在 UserController.javalogin() 方法中。成功登录(为简单起见,示例应用程序不处理诸如密码之类的愚蠢内容)返回带有此 token 的 LoginResponse。然后 Angular 应用程序将其设置为随每个请求一起发送的默认 header 。如果您不想让他们在按 F5 后再次登录,您可以将其存储在本地存储或 cookie 中。再次;这是一个让事情尽可能简单的例子;我故意遗漏了这些东西。

header 由 JwtFilter 类读取并存储在请求上下文中。这样,任何路径都可以访问此信息,而无需解密 header 本身。

尽管在相应的 blog post 中对所有内容进行了相当深入的解释: 有什么不明白的可以私信我。

关于java - JWT 实际上是如何与 Spring MVC 一起创建 token 和验证 token 的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34551390/

相关文章:

java - 我不知道为什么会出现这种情况? "java.nio.channels.UnresolvedAddressException"

java - Spring security - 更改/登录默认路径

spring - 使用 Spring MVC 显示数据库中的图像

java - 让 Spring MVC 与 JodaModule 一起工作

grails - 如何自定义 Spring Security UI 添加字段

java - 如何配置 Spring Security 以使用自定义 AuthenticationManager 实现?

java - 如何将变量放入 OGNL 标记中

java - 在Eclipse调试器中,怎样修改 "hot code replaced"可以进入运行的JVM?

java - 碰撞检测无法正常工作

java - 同一 URL 的 POST 和 GET - Controller - Spring