一文搞定新spring security

是的最近沉迷后端 补一波spring security笔记 最近网上看了好多spring security的帖子 最大的问题就是都太老了 里面的api写法配置什么的全部都是过期 所以整理了一遍新的写发 

1. spring security 是干嘛的

简单干脆的概况就是安全框架 主要包含两方面功能

认证(用户识别) 和 权限

2. 使用

pom文件添加依赖


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

然后此刻我们运行我们的springboot会发现一个神奇的现象

这里我有一个简单接口

package org.example.springtest.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String Hello(){
        return "Hello Word!";
    }
}

访问 http://localhost:8080/hello 

正常来说应该会返回字符串直接 但是现在会直接跳到登录页面,这个登录是框架自动加的 强制我们去登录认证

默认密码就在我们控制台中

默认账号为 user

登录成功后就可以继续访问了

3. 身份验证

这波直接盗图

4. spingsecurity原理

其实他本身就是一条过滤器链,内部包含各种过滤器。

UsernamePasswordAuthenticationFilter 一听就知道负责用户登录的

ExceptionTranslationFiler 处理过滤器链中的异常

FilterSecurityInterceptor 权限校验

5. 自定义认证方案

 除了直接登录之外,还要考虑接下来用户信息的存储和后续校验

(1)登录:

自定义登录接口

  调用providerManager方法认证 成功生成jwt

  把用户信息存进redis中

(2)校验

定义jwt过滤器

获取token

解析token中的userid

从redis中获取

存入SecurityContextHolder

6.开始coding

(1)完成上文的添加maven依赖
(2)创建userDetail实体类 因为在spring security中 对象都是封装在这个实体中的 这是是我们要自定义
package org.example.springtest.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
@NoArgsConstructor
@AllArgsConstructor

public class LoginUser implements UserDetails {
    private SysUser sysUser;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

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

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

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

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

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

    @Override
    public boolean isEnabled() {
        return true;
    }
}
(3)编写userDetailSevice的实现类 逻辑非常的简单
package org.example.springtest.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import jakarta.annotation.Resource;
import org.example.springtest.entity.LoginUser;
import org.example.springtest.entity.SysUser;
import org.example.springtest.exception.AppException;
import org.example.springtest.exception.AppExceptionCodeMsg;
import org.example.springtest.service.SysUserService;
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.Component;

import java.util.Objects;
@Component
public class UserDetailServiceImpl implements UserDetailsService {
    @Resource
    private SysUserService sysUserService;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<SysUser> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("username",username);
        SysUser sysUser=sysUserService.getOne(queryWrapper);
        if(Objects.isNull(sysUser)){
            throw new AppException(AppExceptionCodeMsg.USERNAME_NOT_EXITS);
        }
        return new LoginUser(sysUser);
    }
}

就是查我们自己的表 我的是sys_user 所以引入sysUserSevice 查询之后返回一个通过我们自定义包装的userDetail实体,如果没查到直接抛异常。

(4)编写配置信息

config

注意老版都是要用什么WebSecurityConfigurationAdpter 这些都已经被官方弃用了,新版直接下面这么写就行。

package org.example.springtest.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
// Security自定义配置器
public class SecurityConfig     {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
//    @Bean
//    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//         http
//                //关闭csrf
//                .csrf(AbstractHttpConfigurer::disable)
//                //关闭跨域
//                .cors(AbstractHttpConfigurer::disable)
//                .authorizeHttpRequests((auth) -> auth
//                        .requestMatchers("/api/admin/**").hasRole("ADMIN")
//                        .requestMatchers("/api/user/**").hasRole("USER")
//                        .anyRequest().authenticated()
//                );
//         return http.build();
//
//    }
}

到这一步基本的就完成了,但是这里主要数据库存的密码应该也是通过BCryptPasswordEncoder加密的密码,所以我写了一个测试类来生成密码


    @Autowired
    PasswordEncoder passwordEncoder;

    @Test
    public void generateUserPassWord(){
        System.out.println(passwordEncoder.encode("1234"));
        System.out.println(passwordEncoder.matches("1234","$2a$10$P7ewYAX3DSvtnnQ5Wid8KOAOfQx7PpZ85J5.d9HHqulfyplNaP2I6"));
    }

把生成的1234密码加到数据库

ok然后访问接口发现又跳到了登录页面 输入密码1234 登录成功!最基本的就结束了,简单的用户认证,密码加密。

接下来就是重头戏了 我们不能一直用框架提供的安全登录页面 毕竟现在都是前后端分离,我们需要自己实现自定义的登陆验证。

7.自定义登陆接口

(1)自定义登陆控制层

简单例子了就

package org.example.springtest.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.example.springtest.entity.User;
import org.example.springtest.service.LoginService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@Tag(name = "登录")
@RestController
public class LoginController {
    @Resource
    private LoginService loginService;

    @Operation(summary = "用户登录")
    @PostMapping("/user/login")
    public HashMap<String, Object> login(@RequestBody User user) {
        return loginService.login(user);
    }
}

然后这里的User类型是我自定义的 后面登陆传输数据的格式

package org.example.springtest.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class User implements Serializable {
    private String username;
    private String password;
    private String identifyCode;
}

但是现在问题就来了,未经授权的接口都会被security阻挡,但是我们的登录页面肯定是不需要身份验证的,登录请求也是不需要身份验证就可以访问的,不然没法登录了。

所以接下来去配置security

(2)修改配置

这里我是最新版的写法

package org.example.springtest.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
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;

@Configuration
@EnableWebSecurity
// Security自定义配置器
public class SecurityConfig {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests((auth) -> auth
//                        .requestMatchers("/api/admin/**").hasRole("ADMIN")
//                        .requestMatchers("/api/user/**").hasRole("USER")
                                .requestMatchers("/user/login").permitAll()
                                .anyRequest().authenticated();
                );
        return http.build();
    }
}

这样的话我们/user/login接口就可以跳过身份认证。其他的接口都需要认真,同时呢这里多了一个authenticationManager,这个我们后面登录认证需要使用,本来他是被security默认创建的,但是当我们自定义实现登陆认证,userDetail后,这个默认生产的manager就会返回空,也就是不再默认生成,所以我们需要注入一个新的。

(3)配置实现后就是service业务层了

接口类 非常的朴素就登陆就好

package org.example.springtest.service;

import org.example.springtest.entity.User;

import java.util.HashMap;

public interface LoginService {
     HashMap<String,Object> login(User user);
}

实现类

package org.example.springtest.service.impl;

import jakarta.annotation.Resource;
import org.example.springtest.entity.LoginUser;
import org.example.springtest.entity.User;
import org.example.springtest.exception.AppException;
import org.example.springtest.exception.AppExceptionCodeMsg;
import org.example.springtest.service.LoginService;
import org.example.springtest.utlis.JwtUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Objects;


@Service
public class LoginServiceImpl implements LoginService {
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    RedisTemplate<String,Object> redisTemplate;
    @Override
    public HashMap<String, Object> login(User user) {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
        //验证没通过
        if (Objects.isNull(authentication)) {
            throw new AppException(AppExceptionCodeMsg.USERNAME_NOT_EXITS);
        }
        LoginUser loginUser= (LoginUser) authentication.getPrincipal();
        HashMap<String, Object> hashMap=new HashMap<>();
        Long userId=loginUser.getSysUser().getId();
        hashMap.put("token",JwtUtil.getJwtTokenByUesr(loginUser.getSysUser()));
        hashMap.put("userId", userId);
        redisTemplate.opsForValue().set("loginUser"+userId,loginUser);
        //认证通过生成jwt
        return hashMap;
    }
}

这里同时运用了jwt生产token以及redis缓存用户信息,加一下说明吧这里。

(4)jwt

这里我用的比较简单 这是引入

  <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.11.0</version>
        </dependency>

然后创建工具类 自己编的也比较无脑

package org.example.springtest.utlis;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.example.springtest.entity.SysUser;
import org.springframework.stereotype.Component;

import java.util.Calendar;

@Component
public class JwtUtil {
    private static final String SING = "WangChenTestToken";

    public static String getJwtTokenByUesr(SysUser sysUser) {
        //获取日历对象
        Calendar calendar = Calendar.getInstance();
        //默认7天过期
        calendar.add(Calendar.MINUTE, 30);
        //新建一个JWT的Builder对象
        JWTCreator.Builder builder = JWT.create();
        //将map集合中的数据设置进payload
        builder
                .withClaim("username", sysUser.getUsername())
                .withClaim("email", sysUser.getEmail())
                .withClaim("age", sysUser.getAge())
                .withClaim("sex", sysUser.getSex());

        //设置过期时间和签名
        String sign = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SING));
        return sign;
    }

    /**
     * 验签并返回DecodedJWT
     *
     * @param token 令牌
     */
    public static DecodedJWT getTokenInfo(String token) {
        return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
    }
    public static String getPayLoadValue(DecodedJWT decodedJWT,String key){
        return decodedJWT.getClaim(key).asString();
    }
}
(5)redis

这个就没什么好说的了 看一下其他博主redis怎么玩就行

ok一切就绪 我们直接测试

结果非常的成功,当然了这里我用了统一返回结果封装 可以看我上文,所以返回了该格式,然后我们看一下redis

 

所以也是非常的ok redis也缓存了我们用户的基本信息,ok后续的马上更新 

### Spring Cloud 和 Spring Cloud Alibaba 的介绍 Spring Cloud 是一个基于 Spring Boot 实现的微服务架构开发工具集,它提供了一系列用于快速构建分布式系统的工具,包括配置管理、服务发现、断路器、路由网关等功能[^1]。通过这些工具的支持,开发者可以更加高效地构建和部署复杂的分布式应用程序。 而 Spring Cloud Alibaba 则是在 Spring Cloud 基础上的扩展版本,旨在更好地支持阿里巴巴生态中的技术栈以及国内用户的实际需求[^2]。其主要目标是对现有的 Spring Cloud 功能进行补充和完善,同时集成阿里云的服务和技术能力,从而提升在国内环境下的适用性和性能表现。 --- ### Spring Cloud 和 Spring Cloud Alibaba 的区别 #### 1. **功能范围** - Spring Cloud 提供了一套通用的标准解决方案来解决微服务开发过程中的常见问题,例如服务注册与发现、负载均衡、熔断机制等。 - Spring Cloud Alibaba 不仅继承了 Spring Cloud 的核心特性,还引入了许多针对中国市场的优化措施及特定组件(如 Nacos、Sentinel),使得整个框架更贴合本地化场景的需求[^3]。 #### 2. **技术支持方向** - Spring Cloud 更注重国际化标准协议兼容性,适用于全球范围内多种不同类型的云计算平台之上运行的应用程序开发工作。 - 而 Spring Cloud Alibaba 主要围绕着阿里巴巴集团内部所使用的中间件产品线展开设计,比如 Dubbo RPC 框架和服务治理中心 Nacos 等,因此对于熟悉或者依赖于阿里的技术和基础设施体系结构的企业来说具有更高的契合度。 #### 3. **具体实现差异** 下表展示了两者之间部分重要模块的具体对比情况: | 组件名称 | Spring Cloud 默认实现 | Spring Cloud Alibaba 替代方案 | |----------------|------------------------------------------|----------------------------------------| | 配置管理 | Spring Cloud Config | Nacos | | 服务注册与发现 | Eureka | Nacos 或 Zookeeper | | 断路器 | Hystrix (已停止维护) | Sentinel | | 分布式链路追踪 | Sleuth + Zipkin | SkyWalking | 以上表格清晰表明,在某些方面 Spring Cloud Alibaba 使用了自己的替代品以满足特殊业务场景的要求并提高效率。 #### 4. **社区活跃程度与发展前景** 尽管两个项目都拥有庞大的用户群体和支持团队,但由于后者诞生时间较晚加上背后有强大的商业力量推动,所以在短期内可能会看到更多关于它的更迭代动作发生;不过长期来看二者都会持续演进完善各自的生态系统。 --- ### 示例代码展示 以下是分别使用 `Eureka` 和 `Nacos` 进行简单服务注册的一个例子比较: #### 使用 Eureka 注册服务中心 ```java @SpringBootApplication @EnableEurekaClient public class ServiceApplication { public static void main(String[] args) { SpringApplication.run(ServiceApplication.class, args); } } ``` #### 使用 Nacos 注册服务中心 ```java @SpringBootApplication @NacosDiscoveryProperties(serverAddr = "localhost:8848") public class ServiceApplicationWithNacos { public static void main(String[] args) { SpringApplication.run(ServiceApplicationWithNacos.class, args); } } ``` 上述两段代码片段直观体现了切换到的注册方式只需要改变少量注解即可完成迁移操作。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值