如何使用Spring Session 与 Spring security 完成网站登录改造

技术如何使用Spring Session 与 Spring security 完成网站登录改造如何使用Spring Session 与 Spring security 完成网站登录改造,相信很多没有经验的人对此束手无策,

如何利用Spring Session和Spring security完成网站登录的改造,相信很多没有经验的人都不知所措。因此,本文总结了出现问题的原因和解决方法,希望大家可以通过这篇文章来解决这个问题。

上次,小黑在文章中介绍了分布式一致性会话的四种实现方法。在这四种方法中,最常用的是后端集中存储方案,这样即使web应用程序重新启动或扩展,Session也不会丢失。

如何使用Spring  Session  与 Spring  security  完成网站登录改造

今天,我们使用这种方法来转换会话存储模式,并将其存储在Redis中。

实现方案

我们先来思考一下,如果不依赖任何框架,如何实现后端Session的集中存储。

在这里,我们假设除了我们网站的一些页面,如主页,可以直接访问之外,所有其他页面都需要登录才能访问。

如果我们需要实现这个需求,我们需要验证每个请求。认证的目的是判断用户是否登录以及用户的角色。

如果用户没有登录,我们需要强行将请求跳转到登录页面进行登录。

用户登录后,我们需要存储通过登录Session,获得的用户信息,以便以后的认证请求只需要判断是否有Session.

了解了整个过程之后,就不难体会到原理了。

我们可以使用类似于AOP.的原理,在每个请求进来之后,我们首先判断Session中是否有用户信息,如果没有用户信息,我们将跳转到登录页面。

整个过程如下:

如何使用Spring  Session  与 Spring  security  完成网站登录改造

我们可以用Servelt Filter来实现上面的过程,但是Spring已经为我们实现了整个过程,所以我们不用再做轮子了。

我们可以用Spring-SessionSpring-security来实现上述网站的流程。

正是Spring-Session之春提供了一套管理用户Session.的实现方案,在使用Spring-Session,之后,默认的WEB容器,比如Tomcat,将接管Session.生成的Spring-Session

此外,Spring-Session还提供了几种常见的后端存储实现方案,如Redis、数据库等。

有了Spring-Session,正好帮我们解决了Session.后端的集中存储,但是在上面的过程中,我们还是需要登录授权,可以用Spring-security来实现。

Spring-security可以维护统一的登录授权方式,可以和Spring-Session.一起使用,用户登录授权后,获取的用户信息可以自动存储在Spring-Session.

好了,让我们废话少说,看看实现代码。

下面是使用Spring Boot实现的,Spring-Boot的版本是2.3.2.RELEASE

00-1010首先,我们引入春季会话依赖。这里,我们使用Redis集中存储会话信息,因此我们需要以下依赖项。

属国

groupIdorg.springframework.session/groupId

artifactIdspring-session-data-redis/artifactId

/dependency如果它不是Spring Boot项目,应该引入以下依赖项:

属国

groupIdorg.springfra

mework.data</groupId>
  <artifactId>spring-data-redis</artifactId>
  <version>2.3.0.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-core</artifactId>
  <version>2.3.0.RELEASE</version>
</dependency>

引入依赖之后,我们首先需要在 application.properties增加 Session 相关的配置:

## Session 存储方式
spring.session.store-type=redis
## Session 过期时间,默认单位为 s
server.servlet.session.timeout=600
## Session 存储到 Redis 键的前缀
spring.session.redis.namespace=test:spring:session
## Redis 相关配置
spring.redis.host=127.0.0.1
spring.redis.password=****
spring.redis.port=6379

配置完成之后,Spring Session 就会开始管理 Session 信息,下面我们来测试一下:

@ResponseBody
@GetMapping("/hello")
public String hello() {
    return "Hello World";
}

当我们访问上面地址之后,访问 Redis ,可以看到存储的 Session 信息。

推荐大家一个 Redis 客户端「Another Redis DeskTop Manager」,这个客户端 UI 页面非常漂亮,操作也很方便,下载地址:

https://github.com/qishibo/anotherredisdesktopmanager/releases

如何使用Spring Session 与 Spring security 完成网站登录改造

默认情况下,Session 默认使用HttpSession 序列化方式,这种值看起来不够直观。我们可以将其修改成 json 序列化方式,存储到 redis 中。

@Configuration
public class HttpSessionConfig implements BeanClassLoaderAware {
    private ClassLoader loader;
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer(objectMapper());
    }
    /**
     * Customized {@link ObjectMapper} to add mix-in for class that doesn't have default
     * constructors
     *
     * @return the {@link ObjectMapper} to use
     */
    private ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModules(SecurityJackson2Modules.getModules(this.loader));
        return mapper;
    }
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.loader = classLoader;
    }
}

修改之后 Redis 键值如下所示:如何使用Spring Session 与 Spring security 完成网站登录改造

ps:这里 Redis 键值含义,下次分析源码的时候,再做分析。

Spring Session 还存在一个 @EnableRedisHttpSession,我们可以在这个注解上配置 Spring Session 相关配置。

@EnableRedisHttpSession(redisNamespace = "test:session")

需要注意的是,如果使用这个注解,将会导致 application.properties Session 相关配置失效,也就是说 Spring Session 将会直接使用注解上的配置。

如何使用Spring Session 与 Spring security 完成网站登录改造

这里小黑比较推荐大家使用配置文件的方式。

好了,Spring Session 到这里我们就接入完成了。

Spring security

上面我们集成了 Spring Session,完成 Session 统一 Redis 存储。接下来主要需要实现请求的登陆鉴权。

这一步我们使用 Spring security 实现统一的登陆鉴权服务,同样的框架的还有 Shiro,这里我们就使用 Spring 全家桶。

首先我们需要依赖的相应的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

引入上面的依赖之后,应用启动之后将会生成一个随机密码,然后所有的请求将会跳转到一个 Spring security 的页面。

如何使用Spring Session 与 Spring security 完成网站登录改造

如何使用Spring Session 与 Spring security 完成网站登录改造

这里我们需要实现自己业务的登陆页,所以我们需要自定义登录校验逻辑。

在 Spring security 我们只需要实现 UserDetailsService接口,重写 loadUserByUsername方法逻辑。

@Service
public class UserServiceImpl implements UserDetailsService {
    @Autowired
    PasswordEncoder passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 简单起见,直接内部校验
        String uname = "admin";
        String passwd = "1234qwer";
        // 如果是正式项目,我们需要从数据库数据数据,然后再校验,形式如下:
        // User user = userDAO.query(username);
        if (!username.equals(uname)) {
            throw new UsernameNotFoundException(username);
        }
        // 封装成 Spring security 定义的 User 对象
        return User.builder()
                .username(username)
                .passwordEncoder(s -> passwordEncoder.encode(passwd))
                .authorities(new SimpleGrantedAuthority("user"))
                .build();
    }
}

上面代码实现,这里主要在内存固定用户名与密码,真实环境下,我们需要修改成从数据库查询用户信息。

接着我们需要把 UserServiceImpl 配置到 Spring security 中。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserServiceImpl userService;
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    /**
     * 使用自定义用户服务校验登录信息
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 用户登录信息校验使用自定义 userService
        // 还需要注意密码加密与验证需要使用同一种方式
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }
}

上面的配置中,密码部分我们使用 BCrypt 算法加密,这里需要注意,加密与解密需要使用同一种方式。

接着我们需要实现一个自定义的登陆页面,这里就懒得自己写了,直接使用 spring-session-data-redis 页面。

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org" xmlns:layout="https://github.com/ultraq/thymeleaf-layout-dialect"
      layout:decorate="~{layout}">
<head>
    <title>Login</title>
</head>
<body>
<div layout:fragment="content">
    <!-- 自定义登录的请求    -->
    <form name="f" th:action="@{/auth/login}" method="post">
        <fieldset>
            <legend>Please Login -</legend>
            <div th:if="${param.error}" class="alert alert-error">Invalid username and password.</div>
            <div th:if="${param.logout}" class="alert alert-success">You have been logged out.</div>
            <label for="username">Username</label>
            <input type="text" id="username" name="username"/>
            <label for="password">Password</label>
            <input type="password" id="password" name="password"/>
            <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
            <label>remember me: </label>
            <input type="checkbox" name="remember-me"/>
            <div class="form-actions">
                <button type="submit" class="btn">Log in</button>
            </div>
        </fieldset>
    </form>
</div>
</body>
</html>

这里需要注意一点,这里 form 表单的请求地址使用 /auth/login,我们需要在下面配置中修改,默认情况下登录请求的地址需要为 /login

接着我们在上面的 SecurityConfig 类增加相应配置方法:

/**
 * 自定义处理登录处理
 *
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests((authorize) -> authorize
            .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // 静态资源,比如 css,js 无需登录鉴权
            .anyRequest().permitAll() // 其他页面需要登录鉴权
    ).formLogin((formLogin) -> formLogin  // 自定义登录页面
            .loginPage("/login") // 登录页
            .loginProcessingUrl("/auth/login") // 自定义登录请求地址
            .permitAll()// 登录页当然无需鉴权了,不然不就套娃了吗?
    ).logout(LogoutConfigurer::permitAll // 登出页面
    ).rememberMe(rememberMe -> rememberMe
            .rememberMeCookieName("test-remember") // 自定义记住我 cookie 名
            .key("test") // 盐值
            .tokenValiditySeconds(3600 * 12)) // 记住我,本地生成 cookie 包含用户信息
    ;
}

这个方法可能比较长,重点解释一下:

  • authorizeRequests方法内需要指定那些页面需要鉴权,这里我们指定静态资源无需登录鉴权,其他请求我们都需要登录鉴权

  • formLogin 方法内修改默认的登录页面地址,以及登录的请求地址。

  • logout在这里面可以配置登出的相关配置。

  • rememberMe开启这个功能之后,当内部 Session 过期之后,用户还可以根据用户浏览器中的 Cookie 信息实现免登录的功能。

最后我们需要配置一些页面的跳转地址:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 首页
        registry.addViewController("/").setViewName("home");
        // 登录之后跳转到 home 页
        registry.addViewController("/login").setViewName("login");
    }
}

到此为止,我们已经集成 Spring-SessionSpring-security 完成完整的网站的登录鉴权功能。从这个例子可以看到,引入这个两个框架之后,我们只需要按照 Spring 规范开发即可,其他复杂实现原理我们都不需要自己实现了,这样真的很方便。

看完上述内容,你们掌握如何使用Spring Session 与 Spring security 完成网站登录改造的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注行业资讯频道,感谢各位的阅读!

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/78662.html

(0)

相关推荐

  • Redis的java客户端Jedis的代码怎么写

    技术Redis的java客户端Jedis的代码怎么写这篇文章将为大家详细讲解有关Redis的java客户端Jedis的代码怎么写,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了

    攻略 2021年10月20日
  • 怎么用C语言与java实现kafka avro生产者和消费者

    技术怎么用C语言与java实现kafka avro生产者和消费者本篇内容介绍了“怎么用C语言与java实现kafka avro生产者和消费者”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带

    攻略 2021年11月15日
  • dubbo服务无法调用的原因(dubbo服务已发布却无法调用)

    技术dubbo调不到dubbo服务怎么办这篇文章主要讲解了“dubbo调不到dubbo服务怎么办”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“dubbo调不到dubbo服

    攻略 2021年12月15日
  • listagg在hive中如何使用(listagg函数的使用)

    技术LISTAGG函数怎么使用这篇文章主要介绍“LISTAGG函数怎么使用”,在日常操作中,相信很多人在LISTAGG函数怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”LISTAG

    攻略 2021年12月21日
  • Linux权限位介绍

    技术Linux权限位介绍 Linux权限位介绍Linux 系统,最常见的文件权限有 3 种,即对文件的读(用 r 表示)、写(用 w 表示)和执行(用 x 表示,针对可执行文件或目录)权限。在 Linu

    礼包 2021年12月7日
  • spark具有哪些优点(spark的底层是什么)

    技术Spark核心概念是什么这篇文章主要介绍“Spark核心概念是什么”,在日常操作中,相信很多人在Spark核心概念是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Spark核心概念

    攻略 2021年12月16日