Spring Security 实现记住我

 

开篇一张图,道理全靠悟。

 

示例如下:

1.    新建Maven项目  remember_me

 

2.   pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
        http://maven.apache.org/xsd/maven-4.0.0.xsd">


    <modelVersion>4.0.0</modelVersion>
    <groupId>com.java</groupId>
    <artifactId>remember_me</artifactId>
    <version>1.0.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
    </parent>


    <dependencies>

        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.11</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>



        <!-- 热部署 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
            <version>1.2.8.RELEASE</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>provided</scope>
        </dependency>

    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

 

3.   RememberMeStarter.java

package com.java;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * <blockquote><pre>
 * 
 * 主启动类
 * 
 * </pre></blockquote>
 * 
 * @author Logan
 *
 */
@SpringBootApplication
public class RememberMeStarter {

    public static void main(String[] args) {
        SpringApplication.run(RememberMeStarter.class, args);
    }

}

 

4.   ApplicationContextConfig.java

package com.java.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * 配置文件类
 * 
 * @author Logan
 *
 */
@Configuration
public class ApplicationContextConfig {

    /**
     * <blockquote><pre>
     * 
     * 配置密码编码器,Spring Security 5.X必须配置,否则登录时报空指针异常
     * 
     * </pre></blockquote>
     * 
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

 

5.   RepositoryConfig.java

package com.java.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

/**
 * 数据库相关配置
 * 
 * @author Logan
 *
 */
@Configuration
public class RepositoryConfig {

    @Bean
    public PersistentTokenRepository tokenRepository(DataSource dataSource) {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        // tokenRepository.setCreateTableOnStartup(true); // 第一次启动时可使用此功能自动创建表,第二次要关闭,否则表已存在会启动报错
        return tokenRepository;
    }

}

 

6.   SecurityUserDetailsService.java

package com.java.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * UserDetailsService实现类
 * 
 * @author Logan
 *
 */
@Component
public class SecurityUserDetailsService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // 数据库存储密码为加密后的密文(明文为123456)
        String password = passwordEncoder.encode("123456");

        System.out.println("username: " + username);
        System.out.println("password: " + password);

        // 模拟查询数据库,获取属于Admin和Normal角色的用户
        User user = new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("Admin,Normal"));

        return user;
    }

}

 

7.   LoginConfig.java

package com.java.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

/**
 * 登录相关配置
 * 
 * @author Logan
 *
 */
@Configuration
public class LoginConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PersistentTokenRepository tokenRepository;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()

                // 设置不需要授权的请求
                .antMatchers("/js/*", "/login.html").permitAll()

                // 其它任何请求都需要验证权限
                .anyRequest().authenticated()

                // 设置自定义表单登录页面
                .and().formLogin().loginPage("/login.html")

                // 设置登录验证请求地址为自定义登录页配置action ("/login/form")
                .loginProcessingUrl("/login/form")

                // 设置默认登录成功跳转页面
                .defaultSuccessUrl("/main.html")

                // 添加记住我功能
                .and().rememberMe().tokenRepository(tokenRepository)

                // 有效期为两周
                .tokenValiditySeconds(3600 * 24 * 14)

                // 设置UserDetailsService
                .userDetailsService(userDetailsService)

                // 暂时停用csrf,否则会影响验证
                .and().csrf().disable();
    }

}

 

8.  src/main/resources  下文件如下

 

9.   application.properties

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.32.10:3306/security?useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

 

10.   login.html

<!DOCTYPE html>
<html>

    <head>
        <title>登录</title>
        <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
    </head>

    <body>

        <!--登录框-->
        <div align="center">
            <h2>用户自定义登录页面</h2>
            <fieldset style="width: 300px;">
                <legend>登录框</legend>
                <form action="/login/form" method="post">
                    <table>
                        <tr>
                            <th>用户名:</th>
                            <td><input name="username" /> </td>
                        </tr>
                        <tr>
                            <th>密码:</th>
                            <td><input type="password" name="password" /> </td>
                        </tr>
                        <tr>
                            <th>记住我:</th>
                            <td><input type="checkbox" name="remember-me" value="true" checked="checked" /></td>
                        </tr>
                        <tr>
                            <th></th>
                            <td></td>
                        </tr>
                        <tr>
                            <td colspan="2" align="center"><button type="submit">登录</button></td>
                        </tr>
                    </table>
                </form>
            </fieldset>

        </div>

    </body>

</html>

 

11.   main.html

<!DOCTYPE html>
<html>

    <head>
        <title>首页</title>
        <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
        <script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
        <script>
            function getHostMessage() {
                $.ajax({
                    type: "get",
                    url: "/getHostMessage",
                    async: true,
                    success: function(data) {
                        $("#msg").val(JSON.stringify(data));
                    }
                });
            }
        </script>
    </head>

    <body>

        <div>
            <h2>首页</h2>
            <table>
                <tr>
                    <td><button onclick="getHostMessage()">获取主机信息</button></td>
                </tr>
            </table>

        </div>

        <!--响应内容-->
        <div>
            <textarea id="msg" style="width: 800px;height: 800px;"></textarea>
        </div>

    </body>

</html>

 

12.   js/jquery-3.3.1.min.js 可在官网下载

https://code.jquery.com/jquery-3.3.1.min.js

 

13.   创建数据库

DROP DATABASE IF EXISTS security;
CREATE DATABASE security;
USE security;
create table persistent_logins (
    username varchar(64) not null, 
    series varchar(64) primary key, 
    token varchar(64) not null, 
    last_used timestamp not null
);

 

14.   运行 RememberMeStarter.java , 启动测试

浏览器输入首页  http://localhost:8080/main.html

地址栏自动跳转到登录页面,如下:

 

输入如下信息:

 

User:Logan

Password:123456

 

 单击【登录】按钮,自动跳转到首页。

观察数据库,此时自动生成一条记录,username字段值为登录时使用用户名,记住我Token信息已生成。

如下所示:

 

 

测试【记住我】 功能是否生效

关闭浏览器重新打开,或者关闭系统重新启动,再次访问首页,页面不再跳转到登录页,直接显示首页信息。

如下所示:

 

 

 

 

 

 

 

 

 

 

 

.

posted @ 2019-02-06 20:09  诚信天下  阅读(1689)  评论(0编辑  收藏  举报