SpringSecurity基础学习-身份认证

初始化项目

  • 利用idea生成security项目,仅勾选Spring Web与Spring Security即可

  • 项目生成后删除多余的代码,更改port(非必须,我改为了8888),启动项目
  • 需注意启动日志中的以下内容,此内容为自动生成的登录密码

  • 页面上访问127.0.0.1:8888,登录名为user,密码为上一张图片中生成的密码,点击登录

登录成功,返回首页

登录名与密码分析

从启动日志可以看到,启动时通过调用org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration生成了默认的密码,进入到这个类中,会发现它注入了InMemoryUserDetailsManager的bean

public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties, ObjectProvider<PasswordEncoder> passwordEncoder) {
    SecurityProperties.User user = properties.getUser();
    List<String> roles = user.getRoles();
    return new InMemoryUserDetailsManager(new UserDetails[]{User.withUsername(user.getName()).password(this.getOrDeducePassword(user, (PasswordEncoder)passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build()});
}

从以上代码可知,用户信息是从SecurityProperties获取的,进入到该类中,会发现是一个配置类

进入User类,会发现name和password都是有默认值的,这就是登录名与密码的来源

通过yml文件变更用户名与密码

通过以上分析,我们就可以自己在application.yml中定义用户名和密码了,实例如下

spring:
  security:
    user:
      name: root
      password: root

重启项目,用yml中的配置登录

登录成功

当然,以上两种方式其实并不实用,在真正的项目中,用户名和密码这种信息往往需要存储在数据库中的

Security结合数据库实现登录

pom中引入相关依赖

<!-- 数据库驱动 -->
<dependency>
    <artifactId>mysql-connector-java</artifactId>
    <groupId>mysql</groupId>
    <optional>true</optional>
    <version>8.0.29</version>
</dependency>
<!--        mybatis plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>

<!--    工具    -->
<!--        lombok,非必须 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!--    lang3,非必须   -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

yml中配置相关信息

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: ${SPRING_DATASOURCE_URL:jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false}
    username: ${SPRING_DATASOURCE_USERNAME:root}
    password: ${SPRING_DATASOURCE_PASSWORD:root}
    
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  mapper-locations: classpath:mapper/*.xml

编写Security配置类,暂时只需要配置PasswordEncoder

@Configuration
public class SecurityAutoConfiguration {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

编写Login相关的业务类

登录实体类

package com.learn.security.domain.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.NonNull;

/**
 * @author PC
 * 登录信息
 */
@Data
@TableName("login")
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class Login {

    public static final String FIELD_ID = "id";
    public static final String FIELD_LOGIN_NAME = "login_name";
    public static final String FIELD_PASSWORD = "password";

    @TableId
    private Long id;
    @NonNull
    private String loginName;
    @NonNull
    private String password;
}

登录业务处理类

  • LoginService
package com.learn.security.app.service;

import com.learn.security.domain.entity.Login;

/**
 * @author PC
 * 登录接口
 */
public interface LoginService {
    /**
     * 获取登录信息
     *
     * @param loginName 登录名
     * @return 登录信息
     */
    Login getLoginInfo(String loginName);
}
  • LoginServiceImpl
package com.learn.security.app.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.learn.security.app.service.LoginService;
import com.learn.security.domain.entity.Login;
import com.learn.security.infra.mapper.LoginMapper;
import org.springframework.stereotype.Service;

import java.util.Objects;

/**
 * @author PC
 * 登录实现类
 */
@Service
public class LoginServiceImpl implements LoginService {
    private final LoginMapper loginMapper;

    public LoginServiceImpl(LoginMapper loginMapper) {
        this.loginMapper = loginMapper;
    }


    @Override
    public Login getLoginInfo(String loginName) {
        QueryWrapper<Login> queryWrapper = new QueryWrapper<Login>().eq(Objects.nonNull(loginName), Login.FIELD_LOGIN_NAME, loginName);
        return loginMapper.selectOne(queryWrapper);
    }
}

数据库处理

  • LoginMapper.java
package com.learn.security.infra.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.learn.security.domain.entity.Login;


/**
 * @author PC
 * 登录用mapper
 */
public interface LoginMapper extends BaseMapper<Login> {

}

编写UserDetailsService实现类

package com.learn.security.app.service.impl;

import com.learn.security.app.service.LoginService;
import com.learn.security.domain.entity.Login;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.util.ArrayList;

/**
 * @author PC
 * UserDetails实现类
 */
@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    private LoginService loginService;

    private PasswordEncoder passwordEncoder;

    @Autowired
    public void setLoginService(LoginService loginService) {
        this.loginService = loginService;
    }

    @Autowired
    private void setPasswordEncoder(PasswordEncoder passwordEncoder){
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public UserDetails loadUserByUsername(String loginName) throws UsernameNotFoundException {
        // 1. 查询用户
        Login loginInfo = loginService.getLoginInfo(loginName);
        if (loginInfo == null) {
            //这里找不到必须抛异常
            throw new UsernameNotFoundException("User " + loginName + " was not found in db");
        }

        //2. 获取并加密密码
        Assert.isTrue(StringUtils.isNotEmpty(loginInfo.getPassword()), "password deletion");
        String password = passwordEncoder.encode(loginInfo.getPassword());

        return new User(loginName, password, new ArrayList<>());
    }
}

启动类添加@MapperScan

非必须,也可以在每一个Mapper.java类上添加@Mapper注解

测试

在数据库中设置一个预定义的用户信息

页面上访问http://127.0.0.1:8888/

登录成功

自定义页面

在实际应用中,是不推荐使用security默认的页面的,下面,来演示下如何对这些资源进行自定义

新建页面

  • index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>欢迎登录</title>
</head>
<body>
<form action="/user/login" method="post">
    <div><label> 用户名 : <input type="text" name="username"/> </label></div>
    <div><label> 密码: <input type="password" name="password"/> </label></div>
    <div><input type="submit" value="登录"/></div>
</form>
</body>
</html>
  • success.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎光临</title>
</head>
<body>
<h1>登录成功</h1>
<h1><a href="/logout">登出</a></h1>
</body>
</html>

Security配置类配置自定义页面

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
            //自定义登录页面
            .formLogin()
            //登陆页面设置
            .loginPage("/login.html")
            //登录url设置
            .loginProcessingUrl("/user/login")
            //登录成功后跳转的路径,如果希望跳回原路径,alwaysUse不填或填false
            .defaultSuccessUrl("/success.html", true)
            //允许访问
            .permitAll()
            //设置认证权限
            .and().authorizeRequests().anyRequest().authenticated()
            .and().csrf().disable();
    return http.build();
}

测试

访问http://127.0.0.1:8888/,输入账号密码

点击登录

参考资料

[1]gitee项目仓库地址

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值