SpringSecurity系列——基于SpringBoot2.7的登录接口(内有惊喜)day2-1

2年前 (2022) 程序员胖胖胖虎阿
436 0 0

SpringSecurity系列——基于SpringBoot2.7的登录接口

  • 完整登录
    • 本文所用JWTUtil地址
    • 环境
    • 准备工作
      • 目录
      • 数据库
      • pom.xml
      • 实体类User
      • UserMapper
      • LoginService
      • ResultUtil统一封装返回
      • LoginParam前端表单接收
      • JWTUtil
      • LoginController
    • SpringSecurity配置
      • SpringSecurityConfig
      • UserDetailsServiceImpl
      • UserLogin(UserDetails的实现类)
      • LoginServiceImpl
        • 关于为什么获取用户信息使用authenticate.getPrincipal()方法
    • 使用ApiFox测试

完整登录

本文所用JWTUtil地址

这个JWT工具类是我自己写的,具体怎么使用都在README.md中了
高度封装使用简单
git@gitee.com:giteeforsyf/jjwtutil.git

环境

SpringBoot版本:2.7
SpringSecurity版本:5.4+
注:SpringSecurityConfigurationAdapter已过时

准备工作

在准备工作中我们需要准备数据库、实体类、配置、工具类等

目录

SpringSecurity系列——基于SpringBoot2.7的登录接口(内有惊喜)day2-1

数据库

数据库设计如图:
SpringSecurity系列——基于SpringBoot2.7的登录接口(内有惊喜)day2-1

SpringSecurity系列——基于SpringBoot2.7的登录接口(内有惊喜)day2-1

pom.xml

<dependencies>
        <dependency>
            <groupId>cn.fly</groupId>
            <artifactId>JJWTUtil</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.79</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <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>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

实体类User

package com.example.ss1.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
@TableName("user")
public class User {
    private Long id;
    private String username;
    private String password;
    private String status;
    private String nickname;
}

UserMapper

package com.example.ss1.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.ss1.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

LoginService

package com.example.ss1.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.ss1.entity.User;
import com.example.ss1.parm.LoginParam;
import com.example.ss1.util.ResultUtil;

public interface LoginService extends IService<User> {
    ResultUtil login(LoginParam loginParam);
}

ResultUtil统一封装返回

package com.example.ss1.util;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResultUtil {
    private int code;
    private String Message;
    private Object data;

    public static ResultUtil success(int code, String msg, Object data) {
        ResultUtil ResultUtil = new ResultUtil();
        ResultUtil.setCode(200);
        ResultUtil.setMessage(msg);
        ResultUtil.setData(data);
        return ResultUtil;
    }

    public static ResultUtil success() {
        return success(200, null, null);
    }

    public static ResultUtil success(String msg) {
        return success(200, msg, null);
    }

    public static ResultUtil success(String msg, Object data) {
        return success(200, msg, data);
    }

    public static ResultUtil fail(int code, String msg, Object data) {
        ResultUtil ResultUtil = new ResultUtil();
        ResultUtil.setCode(code);
        ResultUtil.setMessage(msg);
        ResultUtil.setData(data);
        return ResultUtil;
    }

    public static ResultUtil fail(int code) {
        return success(code, null, null);
    }

    public static ResultUtil fail(int code, String msg) {
        return success(code, msg, null);
    }

    public static ResultUtil fail() {
        return success(404, null, null);
    }
}

LoginParam前端表单接收

package com.example.ss1.parm;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class LoginParam implements Serializable {
    private String username;
    private String password;
}

JWTUtil

这里解释一下,由于我直接使用了我自己写的JJWTUtil所以这里只要将该类是设置为配置类继承JJWTUtil即可

package com.example.ss1.util;

import cn.fly.jjwtutil.JJWTUtil;
import lombok.Data;
import org.springframework.context.annotation.Configuration;

@Configuration
@Data
public class JWTUtil extends JJWTUtil {
}

LoginController

package com.example.ss1.controller;

import com.example.ss1.parm.LoginParam;
import com.example.ss1.service.LoginService;
import com.example.ss1.util.ResultUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoginController {
    @Autowired
    private LoginService loginService;

    @PostMapping("/user/login")
    public ResultUtil login(@RequestBody LoginParam loginParam){
        return loginService.login(loginParam);
    }
}

SpringSecurity配置

SpringSecurityConfig

对SpringSecurity的配置类,这里面编译器提示无法自动装配不用管
SpringSecurity系列——基于SpringBoot2.7的登录接口(内有惊喜)day2-1

package com.example.ss1.config;

import com.example.ss1.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;


@Configuration
public class SpringSecurityConfig {
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    
    //获取AuthenticationManager(认证管理器),登录时认证使用
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    //配置过滤
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http.csrf().disable()//关闭csrf
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//关闭session
                .and()
                .authorizeRequests(auth ->
                        auth.antMatchers("/**").permitAll()
                                .anyRequest().authenticated()
                )
                .userDetailsService(userDetailsService).build();
    }

    //配置加密方式
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //配置跨源访问(CORS)
    @Bean
    CorsConfigurationSource corsConfigurationSource(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
}

UserDetailsServiceImpl

package com.example.ss1.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.ss1.entity.User;
import com.example.ss1.entity.UserLogin;
import com.example.ss1.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Objects;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询用户信息
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername, username).last("limit 1");
        User user = userMapper.selectOne(queryWrapper);
        //异常
        if (Objects.isNull(user)){
            throw new UsernameNotFoundException("用户名未发现");
        }
        //查询对应权限信息

        //数据封装为UserDetails返回
        return new UserLogin(user);

    }
}

UserLogin(UserDetails的实现类)

由于我们需要在UserDetailsServiceImpl中将数据进行封装
所以我们就需要有这样一个类实现UserDetails接口,定义返回的一系列方法

package com.example.ss1.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserLogin implements UserDetails {

    //传入用户对象
    private User user;

    /**
     * 判断权限信息
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        return null;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    /**
     * 判断是否未过期
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 判断账户是否未锁定
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 判断是否可以使用
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

LoginServiceImpl

package com.example.ss1.service.impl;

import cn.fly.jjwtutil.JJWTUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.ss1.entity.User;
import com.example.ss1.entity.UserLogin;
import com.example.ss1.mapper.UserMapper;
import com.example.ss1.parm.LoginParam;
import com.example.ss1.service.LoginService;
import com.example.ss1.util.JWTUtil;
import com.example.ss1.util.ResultUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import java.util.Objects;

@Service
public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements LoginService{

    @Autowired
    private JJWTUtil jjwtUtil;
    @Autowired
    private AuthenticationManager authenticationManager;


    @Override
    public ResultUtil login(LoginParam loginParam) {
        //进行用户认证。获取AuthenticationManager authenticate
        //获取认证对象
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginParam.getUsername(), loginParam.getPassword());
        //认证
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        //认证失败
        if (Objects.isNull(authenticate)){
            throw new RuntimeException("登录失败");
        }
        //认证成功,生成token
        //获取用户信息(getPrincipal())
        UserLogin user = (UserLogin) authenticate.getPrincipal();
        Long id = user.getUser().getId();
        Claims claims = Jwts.claims();
        claims.put("userId",id);
        jjwtUtil.defaultBuilder(jjwtUtil);
        String token = jjwtUtil.createToken(claims);
        System.out.println(token);
        //返回
        return ResultUtil.success("登录成功",token);
    }
}

关于为什么获取用户信息使用authenticate.getPrincipal()方法

以下是调试结果,可以看到返回的用户信息是在Principal里的
其实在官网里很明确的说了
SpringSecurity系列——基于SpringBoot2.7的登录接口(内有惊喜)day2-1

使用ApiFox测试

SpringSecurity系列——基于SpringBoot2.7的登录接口(内有惊喜)day2-1

相关文章

暂无评论

暂无评论...