Spring Security Remember me使用及原理详解

Spring Security Remember me是一种通过在用户登录后为用户生成Token,使用户在下一次访问时可以跳过登录,直接使用Token进行自动登录的机制。

实现Remember me功能可以使用Spring Security提供的RememberMeAuthenticationFilter过滤器,该过滤器会在用户登录成功后创建一个Token,将Token信息保存在客户端cookie和服务器端的数据库中,并在下次登录时通过Token自动登录。

下面我们详细讲解Remember me的使用及原理:

设置Remember Me

要启用Remember Me,需要在Security配置文件中启用Remember Me功能,并指定一些参数。

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // ... 其他配置 ...
            .rememberMe()
                .key("uniqueAndSecret")
                .userDetailsService(userDetailsService)
                .tokenValiditySeconds(86400) //有效期1天
        ;
    }

    // ... 其他配置 ...
}

上面的代码添加了以下Remember Me的配置:

  • key("uniqueAndSecret"):指定生成Token时使用的Key,用于加密Token信息;
  • userDetailsService(userDetailsService):指定用户验证时使用的UserDetailsService;
  • tokenValiditySeconds(86400):指定Token的有效期,该例中为1天。

实现UserDetailsService

UserDetailsService是提供用户信息的一个接口,Spring Security需要通过该接口获取用户的信息。我们需要根据实际情况创建一个对象并实现该接口,并在配置中指定该对象。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
        }
        return createUserDetails(user);
    }

    private UserDetails createUserDetails(User user) {
        Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
        for (Role role : user.getRoles()) {
            grantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                grantedAuthorities
        );
    }
}

在上面的代码中,我们使用UserRepository从数据库中获取并返回UserDetails,其中UserDetails包括用户名、密码和用户具有的权限等信息。

如果要使用Remember Me,还需要将UserDetails的密码与Token结合使用,以验证Token的有效性。

自定义Remember Me Cookie

Remember Me标准设置会在客户端生成一个名为remember-me的cookie,如果需要自定义cookie名或其他属性,可以使用rememberMeCookie()方法。

http
    // ... 其他配置 ...
    .rememberMe()
        .key("uniqueAndSecret")
        .userDetailsService(userDetailsService)
        .tokenValiditySeconds(86400) //有效期1天
        .rememberMeCookieName("my-remember-me") //自定义cookie名称
    ;

实现自定义Remember Me Token

如果需要自定义Remember Me Token,可以实现PersistentTokenRepository接口,以便将Token保存在持久化存储中。

1.自定义Token Entity

首先,需要创建一个Token Entity,包含以下字段:

  • String series:Token的series;
  • String username:与Token相关联的用户名;
  • String tokenValue:Token的值;
  • Date date:Token创建时间。
@Entity
@Table(name = "persistent_logins")
public class PersistentLogin {

    @Id
    private String series;
    private String username;
    @Column(name="token", unique=true, nullable=false)
    private String tokenValue;
    @Temporal(TemporalType.TIMESTAMP)
    private Date last_used;

    // ... getter and setter ...

}

2.实现PersistentTokenRepository接口

然后,需要实现PersistentTokenRepository接口,该接口提供了向持久化存储中添加和读取Token的方法。

@Repository
public class PersistentTokenRepositoryImpl implements PersistentTokenRepository {

    @Autowired
    private PersistentLoginRepository persistentLoginRepository;

    @Override
    public void createNewToken(PersistentRememberMeToken token) {
        PersistentLogin login = new PersistentLogin();
        login.setSeries(token.getSeries());
        login.setUsername(token.getUsername());
        login.setTokenValue(token.getTokenValue());
        login.setLast_used(token.getDate());
        persistentLoginRepository.save(login);
    }

    @Override
    public void updateToken(String series, String tokenValue, Date lastUsed) {
        PersistentLogin login = persistentLoginRepository.findById(series).orElse(null);
        if (login != null) {
            login.setTokenValue(tokenValue);
            login.setLast_used(lastUsed);
            persistentLoginRepository.save(login);
        }
    }

    @Override
    public PersistentRememberMeToken getTokenForSeries(String seriesId) {
        PersistentLogin login = persistentLoginRepository.findById(seriesId).orElse(null);
        if (login == null) {
            return null;
        }
        return new PersistentRememberMeToken(
            login.getUsername(),
            login.getSeries(),
            login.getTokenValue(),
            login.getLast_used()
        );
    }

    @Override
    public void removeUserTokens(String username) {
        persistentLoginRepository.deleteAllByUsername(username);
    }

}

在上面的实现中,我们使用了PersistentLoginRepository将Token保存在数据库中。我们使用了Spring Data JPA简化了数据库操作,PersistentLoginRepository中提供了save()和findById()等方法。

3.启用自定义Remember Me Token

使用以上自定义的Token实现时,需要在配置中指定Remember Me使用自定义的Token实现。

http
    // ... 其他配置 ...
    .rememberMe()
        .key("uniqueAndSecret")
        .userDetailsService(userDetailsService)
        .tokenValiditySeconds(86400) //有效期1天
        .rememberMeCookieName("my-remember-me") //自定义cookie名称
        .tokenRepository(tokenRepository()) //自定义Token实现
    ;

@Bean
public PersistentTokenRepository tokenRepository() {
    return new PersistentTokenRepositoryImpl();
}

示例一:使用Remember Me功能

在上面的代码配置完成后,用户登录成功后,记住我功能会在客户端生成一个名为remember-me的cookie。再次访问系统时,如果客户端有该cookie,系统将自动登录。

以下是示例代码:

@Controller
@RequestMapping("/")
public class UserController {

    @GetMapping("/home")
    public String home() {
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        User user = userService.getUserByUsername(username);
        return "Hello, " + user.getUsername() + "!";
    }

    @GetMapping("/login")
    public String showLoginPage(HttpServletRequest request, HttpServletResponse response) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.isAuthenticated()) {
            return "redirect:/";
        }
        return "login";
    }

    @PostMapping("/login")
    public String login(
        @RequestParam String username,
        @RequestParam String password,
        @RequestParam(required = false) boolean rememberMe,
        HttpServletRequest request,
        HttpServletResponse response
    ) {
        UsernamePasswordAuthenticationToken authReq =
            new UsernamePasswordAuthenticationToken(username, password);
        if (rememberMe) {
            authReq.setDetails(new PersistentRememberMeToken("uniqueAndSecret", userDetailsService().loadUserByUsername(username), new SecureRandom().generateSeed(25)));
        }
        Authentication auth = authenticationManager.authenticate(authReq);
        if (auth.isAuthenticated()) {
            SecurityContextHolder.getContext().setAuthentication(auth);
            return "redirect:/";
        } else {
            return "login";
        }
    }

}

在上述代码中,我们使用了rememberMe参数来标志是否使用Remember Me功能,并在登录时向Authentication中添加了一个PersistentRememberMeToken。

示例二:自定义Remember Me页面

在默认情况下,如果Remember Me功能启用,在需要重新登录的时候,系统会自动跳转到默认的Remember Me页面。如果需要自定义该页面,可以使用loginPage()方法指定自定义页面的路径。

以下是示例配置:

http
    // ... 其他配置 ...
    .rememberMe()
        .key("uniqueAndSecret")
        .userDetailsService(userDetailsService)
        .tokenValiditySeconds(86400) //有效期1天
        .rememberMeCookieName("my-remember-me") //自定义cookie名称
        .tokenRepository(tokenRepository()) //自定义Token实现
        .rememberMeParameter("rememberMe") //自定义Remember Me的请求参数名
        .tokenValiditySeconds(3600) //自定义Token有效期
        .useSecureCookie(true) //启用HTTPS,防止cookie被窃取
        .authenticationSuccessHandler(successHandler()) //登录成功后执行的处理
    .and()
    .formLogin()
        .loginPage("/login")
        .loginProcessingUrl("/login")
        .failureUrl("/login?error")
        .usernameParameter("username")
        .passwordParameter("password")
    ;

在指定了登录页面之后,在处理登录失败请求时需要返回正确的视图。示例代码如下:

@Controller
@RequestMapping("/")
public class UserController {

    // ...

    @PostMapping("/login")
    public String login(
        @RequestParam String username,
        @RequestParam String password,
        @RequestParam(required = false) boolean rememberMe,
        HttpServletRequest request,
        HttpServletResponse response
    ) throws IOException {
        UsernamePasswordAuthenticationToken authReq =
            new UsernamePasswordAuthenticationToken(username, password);
        if (rememberMe) {
            authReq.setDetails(new PersistentRememberMeToken("uniqueAndSecret", userDetailsService().loadUserByUsername(username), new SecureRandom().generateSeed(25)));
        }
        Authentication auth = authenticationManager.authenticate(authReq);
        if (auth.isAuthenticated()) {
            SecurityContextHolder.getContext().setAuthentication(auth);
            return "redirect:/";
        } else {
            response.sendRedirect("/login?error");
            return null;
        }
    }

    @GetMapping("/login")
    public String showLoginPage(HttpServletRequest request, HttpServletResponse response) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.isAuthenticated()) {
            return "redirect:/";
        }
        return "login";
    }

    @GetMapping("/login/remember-me")
    public String showRememberMePage() {
        return "rememberMe";
    }

    // ...

}

在login()方法中,如果登录失败,可以使用response.sendRedirect()方法返回OringinURI,即原始请求地址,以便在登录页面中显示错误信息。

在上述代码中,我们自定义了/login/remember-me页面,并使用了rememberMeParameter()方法配置了自定义的Remember Me请求参数名。同时,我们还启用了HTTPS以防止cookie被窃取,在登录成功后使用了authenticationSuccessHandler()方法处理登录成功后的逻辑。

以上就是Spring Security Remember me使用及原理详解的完整攻略。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring Security Remember me使用及原理详解 - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • Java中的ArrayList类常用方法和遍历

    关于Java中的ArrayList类常用方法和遍历,以下是一份详细攻略: ArrayList简介 ArrayList是Java中的一种集合框架,用于存储元素列表,也就是一个动态数组。ArrayList允许我们随意添加、删除、访问列表中的元素,并且会在内部自动调整大小,此外,ArrayList类还提供了一些方便的方法用于操作列表中的元素。 常用方法 下面是Ar…

    Java 2023年5月26日
    00
  • java虚拟机之JVM调优详解

    Java虚拟机之JVM调优详解 在Java应用性能优化过程中,JVM调优是必不可少的一环,它可以通过针对内存、垃圾回收、线程等方面的调优,进一步提高应用程序的性能。本文将介绍JVM调优的一些基本概念和实际操作步骤。 JVM调优基础 JVM内存模型:JVM内存模型包括Java堆、方法区、程序计数器、本地方法栈等,其中Java堆用于承载对象,可以通过调整堆的大小…

    Java 2023年5月26日
    00
  • Java 实现连接sql server 2000

    下面是实现Java连接Sql Server 2000的完整攻略: 准备工作 下载 SQL Server 2000 的 JDBC 驱动包,可从 Microsoft 官网下载。将驱动包放入项目的 lib 目录下。 确认目标 SQL Server 2000 实例正常启动,确保可以连接。 开启目标 SQL Server 2000 的远程连接功能,具体方法可百度“sq…

    Java 2023年5月20日
    00
  • Java 集合框架之List 的使用(附小游戏练习)

    Java 集合框架之 List 的使用 List的概念介绍 在Java的集合框架中,List是其中一个非常重要和常用的容器类。它可以存储有序、可重复的数据集合,并且允许对其中的元素进行增删改查等操作,非常方便。List可以通过下标/索引的方式访问其中的元素,也可以通过迭代器对其中的元素进行遍历。 常用的List实现类有:ArrayList、LinkedLis…

    Java 2023年5月26日
    00
  • 解决kafka消息堆积及分区不均匀的问题

    要解决 Kafka 消息堆积及分区不均匀的问题,需要从多个方面入手。下面是一些攻略和示例: 1. 增加分区数量 如果分区数量不足,可能会导致消息在同一个分区中积累过多,从而导致消息堆积。因此,可以考虑增加分区数量。我们可以通过以下代码示例来实现: # 假设我们要将 topic 的分区数量增加到 10 bin/kafka-topics.sh –zookeep…

    Java 2023年5月20日
    00
  • SpringBoot项目将mybatis升级为mybatis-plus的方法

    下面是详细讲解 SpringBoot 项目将 Mybatis 升级为 Mybatis-Plus 的方法: 一、前置准备 1. 项目环境 SpringBoot版本:2.5.1 Mybatis版本:3.5.4 2. 引入依赖 在项目 pom.xml 中的 dependencies 中,加入以下依赖: <!– Mybatis-plus –> &lt…

    Java 2023年5月20日
    00
  • Java线程代码的实现方法

    下面是详细讲解“Java线程代码的实现方法”的完整攻略。 一、Java线程实现方法 Java中实现线程的方法主要有两种:继承Thread类和实现Runnable接口。两种方法各有优缺点,以下分别进行介绍。 1. 继承Thread类 继承Thread类是实现Java线程的较为简单的方法。继承Thread类后重写run()方法,将run()方法中需要线程执行的代…

    Java 2023年5月18日
    00
  • java配置dbcp连接池(数据库连接池)示例分享

    下面我将为您提供关于“Java配置DBCP连接池(数据库连接池)示例分享”的完整攻略: 什么是DBCP连接池 DBCP连接池是一个Java SQL连接池管理包,用于管理数据库连接的池。它使用JDBC连接接口,并管理连接,可重用连接的对象。 使用DBCP连接池的好处 DBCP连接池的好处如下: 连接池管理:可以重复使用现有的数据库连接,从而大大提高系统的性能和…

    Java 2023年5月19日
    00
合作推广
合作推广
分享本页
返回顶部