SpringSecurity + Mybatis用户登录认证过程

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);
			
	}
}

上一篇:mysql建表语句


下一篇:linux下su切换用户出现This account is currently not available