初始化项目
- 利用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注解
测试
在数据库中设置一个预定义的用户信息
登录成功
自定义页面
在实际应用中,是不推荐使用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项目仓库地址