实现Spring Security的登录认证功能

1.关于Spring Security

一般Spring Security是搭配Spring boot使用的,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。

2.SpringSecurity入门

添加依赖

    <dependencies>
        <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>
    </dependencies>

依赖添加完,Spring Security就默认提供了许多功能将整个应用给保护了起来:

1.要求未经过身份验证的用户必须先经过身份验证才能访问其它接口

2.创建好了默认登录表单,HTTP由基础认证变成了表单认证

3.添加了 CSRF 攻击防护,Session Fixation 攻击防护

3.Spring Security的基础认证和表单认证:

认证方式有无状态简介应用场景
基础认证无状态不使用cookie对外API
表单认证有状态使用session会话网站应用

1、HTTP基础认证

通过HTTP请求头携带用户名和密码进行登录认证

2、HTTP表单认证

Spring Security的默认认证方式是表单认证,使用session会话

Spring Security 用户对象与认证对象

Spring Security的登录认证其实主要就是 找到用户对象,给其认证

1、用户对象

怎么去找用户对象呢?Spring Security默认提供了一些接口

接口名说明
UserDetails用户对象
GrantedAuthority用户权限
UserDetailsService用户对象查询操作
UserDetailsManager创建用户、修改用户密码

因为只涉及到认证,所以只需要实现UserDetails接口,和UserDetailsManager接口就可以了。

其中UserDetails接口的源码中封装了一些对用户的基本操作,getUsername() 和 getPassword()等,我们会用到这两个方法。

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

UserDetailsService接口中只有一个方法loadUserByUsername(String username)方法,作用是根据username去查找用户

我们一般会实现这个接口,重写这个方法来查找用户。

2、认证对象

怎么去给对象认证呢?Spring Security也默认提供了一些接口

接口名说明
Authentication认证请求详细信息
AuthenticationProvider认证的业务执行者
Authentication 中是什么信息?

1、Principal:用户信息,没有认证时一般是用户名,认证后一般是用户对象

2、Credentials:用户凭证,一般是密码

3、Authorities:用户权限

基于MySQL自定义认证过程:

我们是从数据库中查询用户的,所以这里我们用MySQL数据库来存储用户。

建表语句:

根据Bcrypt算法加密密码后的数据

密码和用户名一致

-- 建表
CREATE TABLE `tb_user` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `username` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
  `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
  `nickname` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '昵称',
  `enabled` tinyint NOT NULL DEFAULT '1' COMMENT '账号可用标识',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表';

-- 数据
INSERT INTO `tb_user` VALUES (1, 'zhangsan', '$2a$10$/1XHgJYXtF4g/AiR41si8uvVC6Zc.Z9xVmXX4hO2z.b4.DX.H2j5W', '张三', 1);
INSERT INTO `tb_user` VALUES (2, 'lisi', '$2a$10$PEcF03ina7x9mmt2VbB0ueVkLZWQo/yoKOfvfQpoL09/faBlNuuZ.', '李四', 1);
INSERT INTO `tb_user` VALUES (3, 'wangwu', '$2a$10$PMumxkwwrELTbNDXCj0N4.jD/e/Hv.JiiZTFkdFqlDNLU2TahdYNq', '王五', 1);

代码实现:

创建springboot项目,引入lombok , spirng security , spring boot web , mysql

引入mabatis plus

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>

实体类User

实体类User继承了UserDetails

@Data
 @TableName("tb_user")
 public class User implements UserDetails {
     /**
      * 主键id
      */
     @TableId
     private Long id;
 ​
     /**
      * 用户名
      */
     private String username;
 ​
     /**
      * 密码
      */
     private String password;
 ​
     /**
      * 昵称
      */
     private String nickname;
 ​
     /**
      * 账号可用标识
      */
     private Integer enabled;
 ​
     @Override
     public Collection<? extends GrantedAuthority> getAuthorities() {
         return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
     }
 ​
     @Override
     public boolean isAccountNonExpired() {
         return true;
     }
 ​
     @Override
     public boolean isAccountNonLocked() {
         return true;
     }
 ​
     @Override
     public boolean isCredentialsNonExpired() {
         return true;
     }
 ​
     @Override
     public boolean isEnabled() {
         return true;
     }
 }

UserMapper

UserMapper继承了mybatisplus的baseMapper<T>,封装了对数据库增删改查的方法

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

UserService

UserService继承了mybatis的 IService<T>类

@Service
public interface UserService extends IService<User> {
}

UserServiceImpl类:

作用:只查询用户是否在数据库中是否存在

继承了mtbatis的ServiceImpl<M extends BaseMapper<T> , T> 类

同时实现了UserService(自己的接口) , UserDetailsService(用户对象查询操作的接口)

 @Service
 public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService, UserDetailsService {
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         return null;
     }
 }

controller类:

随便写了一个接口

 @RestController
 public class IndexController {
     @GetMapping("/hello")
     public String hello() {
         return "hello";
     }
 }

UsernamePasswordAuthenticationToken使用

UsernamePasswordAuthenticationToken 是用于封装用户名密码信息的一个类,它实现了Authentication 接口,它的构造方法如下:

public UsernamePasswordAuthenticationToken(Object principal, Object credentials);

 以下代码演示了如何使用 UsernamePasswordAuthenticationToken 对象进行认证:

// 构造用户名密码认证信息
Authentication authentication = new UsernamePasswordAuthenticationToken(username, password);

// 对认证信息进行认证
authentication = authenticationManager.authenticate(authentication);

// 将认证信息存储在 SecurityContextHolder 中
SecurityContextHolder.getContext().setAuthentication(authentication);

 authenticationManager 是一个 AuthenticationManager 对象,用于对认证请求进行认证。如果认证成功,authenticate() 方法会返回一个 Authentication 对象,其中包含了认证请求的详细信息,例如认证主体、认证凭证、授权信息等。最后,将认证信息存储在 SecurityContextHolder 中,用于在应用程序中获取当前的认证信息。

自定义认证类UserAuthenticationProvider:

如果用户在数据库中存在,那么就去认证,检测用户名和密码是否正确,如果都正确,那么返回一个已经认证的Authentication 

 @Component
 @Slf4j
 public class UserAuthenticationProvider implements AuthenticationProvider {
     @Autowired
     private UserDetailsService userDetailsService;
 ​
     // 密码加密
     public PasswordEncoder passwordEncoder() {
         return new BCryptPasswordEncoder();
     }
 ​
     // 自己实现认证过程
     @Override
     public Authentication authenticate(Authentication authentication) throws AuthenticationException {
         // 从Authentication 对象中获取用户名和密码
         String username = authentication.getName();
         String password = authentication.getCredentials().toString();
         // 比对用户名,看是否存在,不存在会抛出异常
         UserDetails user = userDetailsService.loadUserByUsername(username);
         // 用户名存在的话,则比对密码
         if (this.passwordEncoder().matches(password,user.getPassword())) {
             // 密码匹配成功
             log.info("Access Success: " + user);
             return new UsernamePasswordAuthenticationToken(username, password, user.getAuthorities());
         } else {
             // 密码匹配失败
             log.error("Access Denied: The username or password is wrong!");
             throw new BadCredentialsException("The username or password is wrong!");
         }
 ​
     }
 ​
     @Override
     public boolean supports(Class<?> authentication) {
         return true;
     }
 }

基于表单实现认证配置:

 @Configuration
 public class SecurityFormConfiguration {
     @Bean
     public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
         // 启用session会话
         http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
         // 认证方式:Form
         http.authorizeRequests()
                 // 所有请求都需要认证
                 .anyRequest().authenticated()
                 .and()
                 // 启动表单认证模式
                 .formLogin()
                 // 登录页面
                 .loginPage("/login.html")
                 // 请求提交地址
                 .loginProcessingUrl("/login")
                 // 放行上面的两个地址
                 .permitAll()
                 // 设置提交的参数名
                 .usernameParameter("username")
                 .passwordParameter("password")
                 .and()
                 // 开始设置注销功能
                 .logout()
                 // 注销的url
                 .logoutUrl("/logout")
                 // session直接过期
                 .invalidateHttpSession(true)
                 // 清除认证信息
                 .clearAuthentication(true)
                 // 注销成功后跳转地址
                 .logoutSuccessUrl("/login.html")
                 .and()
                 // 禁用csrf安全防护
                 .csrf().disable();
 ​
         return http.build();
     }
 }

static/login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>

<h2>Login</h2>

<form action="/login" method="post">
    <div><label>username:<input type="text" name="username"></label></div>
    <div><label>password:<input type="password" name="password"></label></div>
    <div><input type="submit"></div>
</form>
</body>
</html>

测试

测试http://localhost:8080/hello,看是否跳转到 login登录页面。输入用户名和密码,看是否能够访问/hello接口,页面显示hello

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值