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日

相关文章

  • Win7系统无法安装Java怎么办 Win7系统无法安装Java的故障分析及解决方法

    Win7系统无法安装Java怎么办 如果你在安装Java时遇到了问题,出现无法安装的情况,下面是一些可能的原因和解决方法。 故障分析 操作系统不兼容:Java需要较新的操作系统才能正常运行,Win7系统需要安装最新的更新才能兼容Java。 安装程序有缺陷:安装文件可能损坏或不完整,或者本地安全软件拦截了安装文件。 系统环境变量问题:安装过程中可能操作错误,导…

    Java 2023年5月24日
    00
  • 用java将GBK工程转为uft8的方法实例

    下面是将GBK编码的Java项目转换为UTF-8编码的攻略,包含两个示例说明。 步骤一:备份项目 在进行编码转换之前,务必备份Java项目,以免出现转换失败或其他问题导致数据丢失。 步骤二:使用文本编辑器转换文件编码 使用文本编辑器打开Java项目源文件。 将文件的编码方式从GBK转换为UTF-8。 示例一:使用notepad++进行编码转换。 打开note…

    Java 2023年6月1日
    00
  • 消息队列常见的使用场景

    消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题 实现高性能,高可用,可伸缩和最终一致性架构。最全面的Java面试网站 使用较多的消息队列有 RocketMQ,RabbitMQ,Kafka,ZeroMQ,MetaMQ 以下介绍消息队列在实际应用中常用的使用场景。 异步处理,应用解耦,流量削锋、日志处理和消息通讯五个场景。 场…

    Java 2023年4月17日
    00
  • C#模拟实现抽奖小程序的示例代码

    让我详细讲解一下“C#模拟实现抽奖小程序的示例代码”的完整攻略。 步骤1:确定抽奖方式和奖项 首先确定抽奖的方式和奖项,可以是平等概率、权重抽奖等方式,同时也要定义好奖项的名称和中奖几率。 示例代码: //定义奖项名称 string[] rewardNames = {"一等奖", "二等奖", "三等奖&qu…

    Java 2023年5月19日
    00
  • Java的Struts框架报错“ParameterException”的原因与解决办法

    当使用Java的Struts框架时,可能会遇到“ParameterException”错误。这个错误通常由以下原因之一起: 参数错误:如果请求中的参数不正确,则可能会出现此错误。在这种情况下,需要检查参数以解决此问题。 配置错误:如果配置文件中没有正确配置,则可能会出现此错误。在这种情况下,需要检查文件以解决此问题。 以下是两个实例: 例 1 如果请求中的参…

    Java 2023年5月5日
    00
  • Java 实现对称加密算法

    Java 实现对称加密算法攻略 对称加密算法指使用同一个密钥进行加解密的加密算法。本攻略将介绍 Java 如何实现对称加密算法,主要包括以下内容: 对称加密算法的种类 Java 中实现对称加密的常用类库 对称加密算法的实现步骤 示例说明 对称加密算法的种类 对称加密算法包括 DES、3DES、AES 等常用算法。其中,AES 目前是最常用的对称加密算法。 J…

    Java 2023年5月26日
    00
  • 解决spring项目找不到Aspect依赖注解的问题

    当我们在Spring项目中使用AspectJ时,可能会遇到找不到Aspect依赖注解的问题。这是由于AspectJ依赖的jar文件没有正确添加到项目的classpath中所致。以下是解决该问题的完整攻略: 第一步:添加AspectJ的依赖 在项目的pom.xml中添加以下依赖: <dependency> <groupId>org.as…

    Java 2023年5月31日
    00
  • Node.js在图片模板上生成二维码图片并附带底部文字说明实现详解

    下面是关于“Node.js在图片模板上生成二维码图片并附带底部文字说明实现详解”的完整攻略: 1. 确认需求和准备工作 首先,我们需要明确需求:将一个指定的网址生成二维码图片,并将其和输入的底部文字添加到一个给定的模板图片上,最终生成一张包含二维码和底部文字的图片。 在开始实现之前,我们需要做一些准备工作: 安装 Node.js 和相关依赖; 准备好模板图片…

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