SpringBoot整合SpringSecurity实现用户登录(一)用户验证及JWT令牌生成

SpringBoot整合SpringSecurity实现用户登录验证及JWT令牌生成

登录Demo完整代码仓库地址:https://gitee.com/strivezhangp/java-study-log.git(分支为logindemo)

1 整体概述

​ 本篇介绍了SpringBoot整合以及SpringSecurity实现简单的用户登录效果。未连接数据库,在 .yml文件中定义了用户名和密码, 包含以下功能:

  • 使用SpringBoot初始化项目 SpringBoot2.4.0, JDK版本选用1.8
  • 使用Maven包管理工具
  • 配置整合SpringSecurity
    • 模拟用户登录
    • 生成JWT存入请求头 header

2 实现步骤

2.1 初始化项目

​ 创建一个SpringBoot项目,载入web支持,然后在Maven配置文件中导入一些基本依赖。见下:

​ 依赖及用途介绍:

  • spring-boot-starter-security SpringSecurity注入
  • jjwt JWT生成工具
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- springboot security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- jwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <!-- jwt 版本问题 报错后 需要额外引入的工具类-->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>
    </dependencies>
    
        <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

2.2 SpringSecurity验证流程

​ 以下流程图根据 原作者:https://gitee.com/markerhub/VueAdmin.git 讲解后绘制,只做个人观点。
流程描述

2.3 封装一个可以被序列化的Result类

​ 序列化的Result实现消息的返回。这里涉及到为什么要实现序列化的消息返回,个人见解如下:

远程通信:在Java的RMI(远程方法调用)或通过网络发送对象(如使用HTTP协议发送JSON或XML对象)时,对象需要被序列化,以便可以在网络上传输。在接收端,对象会被反序列化以恢复其原始状态。

/**
 * 返回消息封装类
 */

@Data
public class Result implements Serializable {
    private int code;
    private String msg;
    private Object data;

    private static Result success(int code, String msg, Object data) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);

        return result;
    }

    private static Result success(Object data) {
        return success(200, "操作成功", data);
    }


    private static Result failure(int code, String msg, Object data) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);

        return result;
    }

    private static Result failure(String msg) {
        return failure(400, "操作失败", null);
    }
}

2.4 基本的Security配置

​ 创建一个Security配置类,实现Spring-Security的全局拦截配置,具体参照 2.2 的验证流程。主要配置以下几个部分:

  1. 配置跨域和预防攻击相关选项
  2. 配置登录处理器 成功、失败处理器
  3. 配置session生成规则等
  4. 配置拦截过滤的白名单(一些不需要安全认证拦截【用户登录后才能访问】的请求:项目logologinlogout、验证码获取等)

​ 注意:Security默认登录的请求为 /login 登出请求 /logout 项目整合Security后会自动路由到一个登录页面,实现登陆验证。再没有连接数据库实现用户认证的时候,在 .yml 配置文件中设置用户名等。

spring:
  # spring security 配置
  security:
    user:
      password: 111
      name: 111
/** 部分代码 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // 白名单
    private static final String[] URL_WHITELIST = {存放不需要登陆后才能访问的请求地址}
    
    /** 自动装配必要类*/
    
    // 全局配置
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
                .csrf().disable()  // 跨域配置 关闭预防攻击
                .formLogin() // 登录配置
                .successHandler(userLoginSuccessHandler) // 登录成功处理器
                .failureHandler(userLoginFailureHandler) // 登录失败处理器
                .and()
                .logout().logoutSuccessHandler(userLogOutSuccessHandler) // 登出处理器
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // session配置 生成规则
                .and()
                // 配置白名单 除白名单外,其余链接需要登录后访问
                .authorizeRequests().antMatchers(URL_WHITELIST).permitAll().anyRequest().authenticated();
    }
}

2.5 自定义登录成功处理器-生成JWT令牌

​ 在用户登陆成功后,生成JWT并将其添加到后续的用户请求头部分,确保当前用户访问其他接口时,在请求头部分存有用户认证信息。

​ 实现JWT的生成需要借助JWT生成工具-- jjwt依赖,构建JWT生成类,在此类中注解部分添加了 .yml配置文件中需要配置的JWT属性的前缀,同时定义了一些必要的属性,构建当前用户的JWT,还实现了7天过期的功能。具体类的实现如下:

@Data
@Component
@ConfigurationProperties(prefix = "login.jwt")
public class JwtUtil {
    private long expire; // 过期时长 单位 ms
    private String secret; // JWT密钥
    private String header;
    /**
     * JWT生成
     * @param username
     * @return
     */
    public String generateToken(String username){
        Date nowDate = new Date();
        Date expireDate = new Date(nowDate.getTime() + 1000 * expire); // 计算过期时间
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(username)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, secret) // 加密算法
                .compact();
    }
}
login:
  jwt:
    header: Authorization
    expire: 604800 #过期时间 7 天(单位:秒)
    secret: ji8n3439n439n43ld9ne9343fdfer49h  # 32位的密钥设置

​ 登录成功处理器的定义如下:

@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {
    @Autowired
    JwtUtil jwtUtil;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();

        // 生成JWT 以用户名为JWT签发主题
        String jwt = jwtUtil.generateToken(authentication.getName());
        // 生成的JWT放到请求头中
        httpServletResponse.setHeader(jwtUtil.getHeader(), jwt);

        System.out.println(jwt);

        Result result = Result.success("登录成功");
        outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
        outputStream.flush();
        outputStream.close();
    }
}

​ 注意:由于版本问题可能使用JWT生成工具的时候会导致报错–找不到类等,此外需要2.1初始化项目时 xml文件中额外引入的一些依赖。

​ 测试:PostMan测试后,登录成功后请求头中存在了一个Authorization字段,存放生成的JWT令牌。

测试结果
测试结果

2.6 自定义登陆失败处理器

​ 根据异常类型,返回用户登录失败的原因信息,具体定义如下:

@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        String errorMessage;
        // 判断异常类型是否为一个异常的实例,进而返回不同的错误信息
        if (e instanceof BadCredentialsException) {
            errorMessage = "用户名或密码错误";
        } else {
            errorMessage = "其他失败原因";
        }
        Result result = Result.failure(errorMessage);
        outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
        outputStream.flush();
        outputStream.close();
    }
}

2.7 自定义用户登出处理器

​ 用户登出的操作,security默认请求路径为 /logout ,登出的时候首先判断用户是否存在身份凭证信息,然后清除令牌信息。

@Component
public class UserLogOutSuccessHandler implements LogoutSuccessHandler {
    @Autowired
    JwtUtil jwtUtil;

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        // 判断是否有身份凭证
        if (authentication != null) {
            new SecurityContextLogoutHandler().logout(httpServletRequest, httpServletResponse, authentication);
        }

        httpServletResponse.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        httpServletResponse.setHeader(jwtUtil.getHeader(), "");
        outputStream.write(JSONUtil.toJsonStr(Result.success("退出成功")).getBytes("UTF-8"));
        outputStream.flush();
        outputStream.close();
    }
}

附:自定义登录登出路径的配置

​ 当然,如果没有自定义这些请求表单、访问的路径,security将会自动绑定的请求路径是 /login , 登出路径为 /logout

​ 在security配置类中定义如下:

http  
    // 配置请求授权  
    .authorizeRequests()  
        .antMatchers("/login**", "/custom-logout**").permitAll() // 允许所有人访问登录和登出路径  
        .anyRequest().authenticated() // 其他所有请求都需要认证  
        .and()  
    // 配置表单登录  
    .formLogin()  
        .loginPage("/custom-login") // 自定义登录页路径  
        .loginProcessingUrl("/perform-login") // 自定义登录处理路径  
        .permitAll()  
        .and()  
    // 配置登出  
    .logout()  
        .logoutUrl("/custom-logout") // 自定义登出请求路径  
        .logoutSuccessUrl("/custom-logout-success") // 自定义登出成功后的跳转路径  
        .permitAll();  

附:未进行跨域配置登录请求中的一些参数说明

​ 在Spring Security中,_csrf字段是跨站请求伪造(Cross-Site Request Forgery,简称CSRF)防护机制的一部分。CSRF是一种攻击方式,攻击者通过伪造用户的请求来执行未经授权的操作。

Spring Security使用同步令牌模式来防止CSRF攻击。每当一个表单被渲染到客户端时,Spring Security会生成一个唯一的CSRF令牌并将其附加到表单数据中。当用户提交表单时,浏览器会将这个令牌一起发送回服务器。服务器会验证这个令牌是否有效,并且是否与会话中存储的令牌相匹配。如果令牌无效或不匹配,请求将被拒绝,从而防止CSRF攻击。

​ 在未进行跨域的时候 login 请求携带一个参数:_csrf=0b229dba-1f13-48da-b47a-96ee254aa007就是这样一个CSRF令牌。它确保了表单提交请求是来自合法用户且未被篡改的。_csrf字段用于在客户端和服务器之间传递CSRF令牌,以提供对CSRF攻击的保护。

​ 消除这个字段的影响可以在Security配置文件中设置如下:

http.csrf().disable();
后续将实现整合MybatisPlus的用户查库验证以及图片验证码功能。
  • 26
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot可以很方便地整合Spring SecurityJWT(JSON Web Token)。 首先,需要在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 然后,需要创建一个Security配置类,用于配置Spring SecurityJWT: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Autowired private JwtRequestFilter jwtRequestFilter; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // 配置用户认证方式 } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.csrf().disable() .authorizeRequests().antMatchers("/authenticate").permitAll(). anyRequest().authenticated().and(). exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); } } ``` 在上面的配置类中,需要注入JwtAuthenticationEntryPoint和JwtRequestFilter两个类。其中,JwtAuthenticationEntryPoint用于处理未经授权的请求,JwtRequestFilter用于验证JWT并将用户信息添加到Spring Security上下文中。 接下来,需要创建一个JwtTokenUtil类,用于生成验证JWT: ```java @Component public class JwtTokenUtil { private static final String SECRET_KEY = "secret"; public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return Jwts.builder().setClaims(claims).setSubject(userDetails.getUsername()) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) .signWith(SignatureAlgorithm.HS512, SECRET_KEY).compact(); } public boolean validateToken(String token, UserDetails userDetails) { final String username = getUsernameFromToken(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } private boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } private Date getExpirationDateFromToken(String token) { return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getExpiration(); } private String getUsernameFromToken(String token) { return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject(); } } ``` 在上面的类中,需要设置一个密钥,用于生成验证JWT。generateToken方法用于生成JWT,validateToken方法用于验证JWT是否有效。 最后,需要创建一个JwtAuthenticationEntryPoint类和一个JwtRequestFilter类。JwtAuthenticationEntryPoint类用于处理未经授权的请求,JwtRequestFilter类用于验证JWT并将用户信息添加到Spring Security上下文中。 以上就是整合Spring SecurityJWT的基本步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值