JWT如何实际与Spring MVC一起使用来创建令牌和验证令牌?

时间:2015-12-31 20:33:02

标签: java spring-mvc spring-security jwt

我实际上希望对JWT概念及其与Spring MVC的工作原理有更多的了解。我找到了链接https://github.com/nielsutrecht/jwt-angular-spring,其程序运行得非常好。 但我想知道它是如何创建令牌的,然后是用户如何登录应用程序。我需要澄清/回答它是如何工作的?请做好。

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

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

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));
    }
}

context.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>

EDIT-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 ----

2 个答案:

答案 0 :(得分:4)

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

示例应用程序使用Jjwt库来创建和解密JSON Web令牌。在我的示例应用程序中,当一个人成功登录时创建令牌。这种情况发生在hub: image: selenium/hub ports: - "4444:4444" firefox: image: selenium/node-firefox links: - hub chrome: image: selenium/node-chrome links: - hub 中的login()方法中。成功登录(示例应用程序不会为了简单而处理像密码这样的愚蠢内容)会返回带有此令牌的UserController.java。角度应用程序将此设置为每个请求发送的默认标头。如果您不希望在点击F5后再次登录,则可以将其存储在本地存储或cookie中。再次;这是一个让事情尽可能简单的例子;我故意把这些事情遗弃。

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

尽管在相应的blog post中对所有内容都进行了深入的解释:请告诉我您是否有任何理解。

答案 1 :(得分:2)

当您执行登录时,您通过HTTP post请求发送用户名和密码。如果凭据正确,Java Web服务将使用令牌进行响应。前端AngularJS应用程序将令牌存储到LocalStorage或Cookie中。在您发出的每个下一个请求时,令牌将作为Authorization标头的值发送。 Java Web服务将拦截请求并检查令牌的存在和有效性。

你也可以看一下这个demo application