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读取cvs文件并导入数据库

    敬爱的读者,首先感谢您对 Java 编程的热爱。关于如何从CSV文件中读取数据并将其导入数据库,本文将提供一个完整的攻略,详细介绍每个步骤。在本文中,我们将使用Java编写代码来实现该功能。 1. 准备CSV文件 首先,需要准备好包含数据的 CSV 文件。CSV 文件是一种纯文本格式,用于存储和交换以逗号、制表符、分号等分隔符隔开的数据。你可以使用 Micr…

    Java 2023年5月20日
    00
  • webuploader+springmvc实现图片上传功能

    前提条件在使用webuploader+springmvc进行图片上传之前,需要确保以下条件已准备就绪: 服务器环境: JDK:1.8及以上; Tomcat:7.0及以上; SpringFramework:4.0.9及以上; Maven或Gradle; webuploader插件。 整体思路: 利用webuploader插件进行文件上传,前端通过ajax向服务…

    Java 2023年6月15日
    00
  • 浅谈JS如何写出漂亮的条件表达式

    下面是详细讲解“浅谈JS如何写出漂亮的条件表达式”的完整攻略: 1. 使用三元运算符 三元运算符是一种简洁的条件表达式语法,可以用来简化if-else语句的编码。三元运算符包含一个条件判断语句和两个表达式,形式如下: condition ? expression1 : expression2 其中,condition是一个布尔表达式,如果计算结果为true,…

    Java 2023年6月15日
    00
  • JavaWeb实现文件上传下载功能实例解析

    JavaWeb实现文件上传下载功能实例解析 一、文件上传 文件上传是指将本地机器上的文件通过网络传输到远程服务器上的过程。在JavaWeb中,可以使用Servlet实现文件上传功能。 在上传文件之前,需要先创建一个表单,让用户选择需要上传的文件。具体操作如下: 在HTML中创建一个表单,指定表单的enctype属性值为”multipart/form-data…

    Java 2023年5月20日
    00
  • Java常用命令汇总

    Java常用命令汇总攻略 Java是一种高级编程语言,由于其稳定性和跨平台性能备受欢迎,因此成为了许多软件的首选语言。针对Java的常用命令,本文旨在为初学者提供帮助以及提高Java编程效率。下面将对Java常用命令进行详细讲解。 Java编译命令 Java编写的代码在开发完成后需要编译成可执行的文件。下面是Java编译命令的格式和用法: javac [op…

    Java 2023年5月19日
    00
  • Java实现简单的银行管理系统的示例代码

    下面我将详细介绍如何实现一个简单的银行管理系统,包括设计思路、代码实现和示例演示。 设计思路 这个银行管理系统需要实现以下功能:1. 新增账户2. 存款3. 取款4. 查询账户信息 考虑到以上需要,我们可以设计出如下的类结构:- Account类,用于存储账户信息,包括账户号、姓名、余额等属性,以及存款和取款的方法。- Bank类,用于管理所有的账户,包括新…

    Java 2023年5月19日
    00
  • 详解spring+springmvc+mybatis整合注解

    详解Spring+SpringMVC+MyBatis整合注解 Spring、SpringMVC和MyBatis是Java Web应用程序开发中常用的框架。在本文中,我们将介绍如何将这三个框架整合在一起,并使用注解来简化配置。 步骤1:添加依赖 首先,我们需要在pom.xml文件中添加Spring、SpringMVC和MyBatis的依赖。以下是一个简单的依赖…

    Java 2023年5月17日
    00
  • SpringMVC中重定向model值的获取方式

    在SpringMVC中,重定向到页面时,我们想要将一些值传递给下一个页面,这些值需要被设置在model中。下面是完整攻略: 1. 在Controller中设置重定向的model值 在Controller中设置model值并将请求重定向到另一个页面时,我们需要使用RedirectAttributes接口。可以使用addAttribute()方法将值添加到mod…

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