1、Maven依赖
主要有mybatis-springboot依赖、jdbc驱动、security依赖、lombok、web。
<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>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
2、编写登录页面
-
编写一个简单的自定义的登录页面表单,用于提交前端的请求。
-
注意这里的标签中name属性,默认使用username,password。如果更换需要在Security的认证请求中进行参数名的绑定
<form action="/user/login" method="post">
用户名: <input type="text" name="username" placeholder="请输入账号"><br/>
密码: <input type="password" name="password" placeholder="请输入密码"><br/>
<input type="submit" value="登录">
</form>
- 编写一个简单的响应页面,注意这里不需要编写表单中的提交处理
@Controller
public class RouterController {
@RequestMapping(value = {"/", "/index", "/index.html"})
@ResponseBody
public String index(){
return "Auth Pass!";
}
3、创建数据库和POJO
- 在数据库中创建一张表,并编写对应的pojo
create table `account` (
`id` int(11) not null auto_increment comment '编号',
`username` varchar(30) not null comment '姓名',
`password` varchar(30) not null comment '密码',
`role` varchar(100) not null comment '权限',
primary key (`id`)
) engine=innodb default charset=utf8
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
private Integer id;
private String username;
private String password;
private String role;
}
4、编写Mapper
- 首先编写一个AccountMapper接口,并且使用@Mapper注解以及@Repository注解标记
@Mapper
@Repository
public interface AccountMapper {
Account getLoginAccount(String username);
}
- 在resources下创建mybatis/mapper文件夹,并且存放一个AccountMapper.xml(与接口同名)
<?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="com.splay.mapper.AccountMapper">
<select id="getLoginAccount" parameterType="string" resultType="Account">
select *from account where username = #{username}
</select>
</mapper>
5、连接数据库
- 首先在application.yml配置文件中载入驱动、并且绑定mybatis的mapper.xml文件
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
type-aliases-package: com.splay.pojo
6、编写自定义实现类
-
这是一个特殊的实现类,需要实现UserDetailsService接口;并且重写里面的loadUserByUsername方法。
-
并且在这里注入AccountMapper进行,在这里对数据库中的数据进行查询校验。
-
查询的时候只需要传入提交上来的用户名,然后去查询一个用户;将这个用户信息封装到User类中(Security中的Use类)。
-
并且查询出来的密码会加密,因此还需要注入一个专门用于密码加密解密的类。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
// 注入dao层
@Autowired
AccountMapper mapper;
// 密码加密
@Autowired
BCryptPasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = mapper.getLoginAccount(username);
System.out.println(account.toString());
List<GrantedAuthority> list = new ArrayList<>();
// SpringSecurity权限控制角色需要使用"ROLE_"开头。
list.add(new SimpleGrantedAuthority("ROLE_" + account.getRole()));
// 密码在构造时需要进行加密, 否则会报错
return new User(passwordEncoder.encode(account.getUsername()), passwordEncoder.encode(account.getPassword()), list);
}
}
8、Security的配置
-
继承WebSecurityConfigurerAdapter类,并且使用@EnableWebSecurity注解标记
-
先配置请求处理方式,自定义页面参数等等…
-
使用UserDetailsServiceImpl为自定义的验证方式进行数据库的操作
-
@EnableWebSecurity继承自@Configuration注解,有IOC容器的功能,因此可以直接注入上下文依赖的加密解密类。
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().and().
formLogin()
.usernameParameter("username") // 绑定前端的参数名
.passwordParameter("password") // 绑定前端的参数名
.loginPage("/login") // 登录页面
.loginProcessingUrl("/user/login") // 提交表单处理的请求,由Security实现
.defaultSuccessUrl("/index",true) // 每次验证成功自动跳转到哪里
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 密码需要进行加密解密进行验证匹配!
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
9、关于密码的校验
-
重点:明面上并没有看到密码匹配,甚至没有办法拿到前端传过来的密码与数据库中的密码进行校验;但是Security在内部已经偷偷给你做了校验
-
DaoAuthenticationProvider类中的additionalAuthenticationChecks方法自动进行了校验。
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
....
@Override
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
....
/***
* 2. 这里拿到数据库中查询到的,被封装到User类中的数据
*
* presentedPassword是前端传过来的密码,将其使用相同的算法进行加密
* userDetails.getPassword()是数据库中查询之后加密的密码
* 然后二者进行匹配校验
*/
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
@Override
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// 1. 这里会加载到我们编写的UserDetailsServiceImpl实现类进行查询校验
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
}
}