前言
本文将介绍自定义逻辑实现数据库查询并登录。
使用Spring Security自定义逻辑需要我们重写Spring Security框架中的UserDetailsService接口方法
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
可以看到,UserDetailsService
中仅定义了loadUserByUsername
方法,用于返回一个UserDetails
对象
事实上,UserDetails对象包含了一系列在验证时,需要的信息,例如用户名、密码、权限等等。
Spring Security会根据这些信息判断该用户是否认证成功并授权。
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
数据库联动实现用户登录认证
既然要查数据库,那么我们先搞一下环境吧!
创建数据库temp,并新建user表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
`password` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增',
`enable` tinyint(2) NOT NULL COMMENT '该用户是否可登录',
`roles` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户角色',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('sunnyboy', '123456', 1, 1, 'admin');
这里使用Mybatis作为ORM框架,完整的pom如下图所示,主要引入了Spring Boot依赖和Spring Security、Mybatis和一些例如Lombok的工具依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>SpringSecurityDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
</project>
按照Mybatis的开发模式,应该编写UserDao,需要注意的是,这里指定@Mapper注解用于声明这是个Mapper,就不需要在启动类上加注解@MapperScan了,当然,反过来也是一样的,两者选其一即可。
@Mapper
public interface UserDao {
User selectByUsername(@Param("username") String username);
}
对应查询Mapper文件如下,基本语法这里不再赘述。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="ssd.dao.UserDao">
<resultMap id="BaseResultMap" type="ssd.pojo.User">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="username" jdbcType="VARCHAR" property="username" />
<result column="password" jdbcType="VARCHAR" property="password" />
<result column="enable" jdbcType="TINYINT" property="enable" />
<result column="roles" jdbcType="VARCHAR" property="roles" />
</resultMap>
<sql id="Base_Column_List">
id, username, `password`, `enable`, roles
</sql>
<select id="selectByUsername" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from user
where username = #{username,jdbcType=VARCHAR}
</select>
</mapper>
新建User对象,需要注意的是,这里User需要实现 UserDetails接口.这是因为我们重写Service实际上是在改逻辑的中间一段,最后还是要返回给Spring Security UserDetails
对象的。
在继承UserDetails后,需要重写isAccountNonExpired、 isAccountNonLocked、isCredentialsNonExpired
这三个方法,这三个方法代表的是账户的可用状态,这里我们暂时都给按账户是可用的,故都设置为返回true。
/**
* user
* @author
*/
@Data
public class User implements Serializable, UserDetails {
/**
* 主键自增
*/
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 该用户是否可登录
*/
private Boolean enable;
/**
* 用户角色
*/
private String roles;
private static final long serialVersionUID = 1L;
/**
* 允许的权限
*/
private List<GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
public void setAuthorities(List<GrantedAuthority> authorities) {
this.authorities = authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.enable;
}
}
重写UserService接口,并且需要在config中指定userService为我们重写的这个。
并且考虑到一般不会使用spring security自带的登陆界面,这里自定义自己的登陆界面
实现自定义登陆界面很简单,只需要在继承WebSecurityConfigurerAdapter
的类中声明以下html静态界面的位置即可。
/**
* @author 阳光大男孩!!!
*/
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 指定我们自己实现的userDetailService
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//自定义自己编写的登录页面
http.formLogin()
.usernameParameter("username")
.passwordParameter("password")
//登录页面设置
.loginPage("/Login.html")
//登录访问路径
.loginProcessingUrl("/user/login")
// 登录成功跳转目标url
.defaultSuccessUrl("/hello").permitAll()
// 设置不需要认证的路径
.and().authorizeRequests().antMatchers("/","/index","/user/login").permitAll()
// 设置所有请求都需要认证
.anyRequest().authenticated()
//设置csrf保护暂时关闭
.and().csrf().disable();
}
@Bean
PasswordEncoder password()
{
return new BCryptPasswordEncoder();
}
}
/**
* @author 阳光大男孩!!!
*/
@Service("userDetailsService")
@RequiredArgsConstructor
@Slf4j
public class SecurityUserDetailsServiceImpl implements UserDetailsService {
private final UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println(username);
User user = userDao.selectByUsername(username);
if(user==null)
{
throw new UsernameNotFoundException("用户不存在");
}
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
user.setAuthorities(auths);
System.out.println(user);
user.setPassword(new BCryptPasswordEncoder().encode(user.getPassword()));
return user;
}
}
这里简单自定义了一个登陆界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<div id="login">
<h1>Login</h1>
<form method="post" action="/user/login">
<input type="text" required="required" placeholder="用户名" name="username"></input>
<input type="password" required="required" placeholder="密码" name="password"></input>
<button class="but" type="submit" >登录</button>
</form>
</div>
</body>
</html>
考虑登录成功后应当跳转到某个界面,为简单起见,需要设置登陆成功的界面
/**
* @author 阳光大男孩!!!
*/
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello Spring Security";
}
}
指定配置文件内容
server.port=8080
#spring.security.user.name=wyh
#spring.security.user.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.application.name=spring-security-demo
# 配置数据库路径信息
spring.datasource.url=jdbc:mysql://localhost:3306/temp
spring.datasource.username=root
spring.datasource.password=123456
# 指定mapper文件路径
mybatis.mapper-locations=classpath:mapper/*.xml
测试
按照之前的设定,我们指定/hello为受保护的资源路径。
故需要认证授权,会跳转到登陆界面
登录成功后自动跳转到/hello
,也就是资源路径