文章目录
实验一、引入spring security
①、pom文件添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
②、编写controller测试
@Controller
public class UserContorller {
@GetMapping("/hello")
@ResponseBody
public String hello(){
return "hello hello" ;
}
}
③、启动应用测试:localhost:8080/hello 发现需要经过spring security的登录认证路径 /login
,其中用户名默认为user,密码是随机生成的uuid,打印在控制台中。
源码分析:
在UsernamePasswordAuthenticationFilter#attemptAuthentication
方法中,已经定义了默认的username和password字段,用户提交上来的表单信息将在http
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
//创建路径匹配器
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST");
private String usernameParameter = "username";
private String passwordParameter = "password";
private boolean postOnly = true;
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
else {
// 从request中获取用户名和密码
String username = this.obtainUsername(request);
username = username != null ? username : "";
username = username.trim();
String password = this.obtainPassword(request);
password = password != null ? password : "";
//生成一个用户名和密码身份验证的令牌 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
//设置身份请求的认证信息
this.setDetails(request, authRequest);
//返回一个经过身份验证的对象
return this.getAuthenticationManager().authenticate(authRequest);
}
}
// 从Request中获取参数
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(this.passwordParameter);
}
实验二、从mysql中加载用户
前置知识
-
spring security 不关心要登录的用户名称和密码从哪里来,它们可以来自配置文件,内存或者是数据库。
-
Spring Security 提供了一个配置类让我们配置它的主要行为:
WebSecurityConfigurerAdapter
这个类有三个重要的重载函数,通过重写它们我们可以做许多工作,如定义spring security的访问规则,拦截规则,认证来源等等
方法 | 描述 |
---|---|
configure (WebSecurity) | 配置spring security的filter链 |
configure (HttpSecurity) | 配置如何通过拦截器保护请求 |
configure (AuthenticationManagerBuilder) | 配置userDetail服务 |
⭐AuthenticationManagerBuilder
用来配置全局的认证相关的信息,核心是AuthenticationProvider和UserDetailsService,分别提供不同的认证方式(如密码+用户名或密码+邮箱/手机等)和自定义用户的细节。
这个类中有几个方法可以实现不同用户的存储,如InMemoryUserDetailsManagerConfigurer
是基于内存的,JdbcUserDetailsManagerConfigurer
是基于Jdbc方式的
- 而
userDetailsService
方法就是本实验的重点,允许我们自定义用户的实现。
它接收一个userDetailsService
类的参数,返回一个DaoAuthenticationConfigurer
//AuthenticationManagerBuilder.java部分源码
public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication() throws Exception {
return (InMemoryUserDetailsManagerConfigurer)this.apply(new InMemoryUserDetailsManagerConfigurer());
}
public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication() throws Exception {
return (JdbcUserDetailsManagerConfigurer)this.apply(new JdbcUserDetailsManagerConfigurer());
}
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(T userDetailsService) throws Exception {
this.defaultUserDetailsService = userDetailsService;
return (DaoAuthenticationConfigurer)this.apply(new DaoAuthenticationConfigurer(userDetailsService));
}
⭐HttpSecurity
用于权限规则的配置,提供了许多相关的方法,通过这些方法的组合,可以自定义登录,登出的url地址,页面,url拦截规则等等。
它被设计成链式调用,在执行每个方法后,都会返回一个预期的上下文,便于连续调用,不需要关心每个方法到底返回了什么
authorizeRequests() //返回一个url拦截注册器,
//可以调用它提供的anyRequest(),antMachers(),和regexMatchers()等方法匹配系统的url
formLogin()
httpBasic()
//这两个方法都声明了spring securtiy提供的表单认证方法,分别返回对应的配置器
csrf() //spring security提供的跨站请求伪造防护功能,默认开启
logout() //登出相关
and()
configure (HttpSecurity)的默认方法如下:
protected void configure(HttpSecurity http) throws Exception {
this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http.authorizeRequests((requests) -> {
((AuthorizedUrl)requests.anyRequest()).authenticated();
});
http.formLogin();
http.httpBasic();
}
可以看到它已经有一些默认的特性:
- 所有请求都需要认证
- 允许表单登录进行身份验证
- 允许用户使用http基本认证
知识点:
http基本认证是RFC2616中定义的一种认证模式,有4个步骤:
- 客户端发起一条没有携带认证信息的请求
- 服务器返回一条401Unauthorized响应,并在WWW-Authentication首部认证说明认证形式,当进行http基本认证后,WWW-Authentication会被设置为Basic realm=“被保护页面”
- 客户端收到401 Unauthorized响应后,弹出对话框,询问用户名和密码。当用户完成后,客户端将用户名和密码使用冒号拼接并编码为Base64形式,然后放入请求Authorization首部发送给服务器。
- 服务端解码从客户端发来的用户名和密码,并验证正确后,返回客户端请求的报文。
⭐WebSecurity
配置全局请求忽略规则,一般用来放行静态资源,如html,css,js等
回到实验,我们想要从Mysql数据库中查询出用户的信息给spring security校验,关键在于把得到的数据包装成它认识的样子,于是它提供了 UserDetailsService
和 UserDetails
,我们直接看源码:
UserDetail是个接口,定义了用户的信息
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
UserDetailsService 也是个接口,而且只有一个方法,就是根据参数username
来查找用户,并返回一个UserDetails
对象
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
实现UserDetails接口定义需要认证的用户,实现UserDetailsService定义如何从username获得用户对象。
①、编写对象实现UserDetail接口
@Data
public class User implements UserDetails {
Long id ;
String userName ;
String passWord ;
boolean enabled ;
List<GrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return passWord;
}
@Override
public String getUsername() {
return userName;
}
//账户是否过期
@Override
public boolean isAccountNonExpired() {
return true;
}
//账户是否被锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
// 凭证是否过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//用户是否可用
@Override
public boolean isEnabled() {
return enabled;
}
}
②、实现UserDetailsService,这里使用了mysql+mybatis
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper ;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//从数据库中查,下文有配置
User user = userMapper.getUserByName(username);
if(user==null) {
throw new RuntimeException("用户不存在!");
}
return user ;
}
}
③、数据库配置和创建表:
- pom文件添加依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
- 配置数据库: 在application.properties文件中完成数据库的配置,注意替换成你自己的数据库信息
spring.datasource.type= com.alibaba.druid.pool.DruidDataSource
spring.datasource.url= jdbc:mysql:///${你的数据库名称}?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username= root
spring.datasource.password=${你的数据库密码}
# 指定sql映射文件的位置
mybatis.mapper-locations = classpath*:com/wingchi/mapper/*.xml
- 创建用户表,并插入数据
(博主的用户表是项目中在用的,有一些其他的字段不需要理会)
CREATE TABLE `user` (
`id` int(10) PRIMARY KEY AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`password` varchar(15) NOT NULL,
`createTime` datetime,
`lastLoginTime` datetime,
`enabled` boolean NOT NULL
)ENGINE=InnoDB CHARSET=utf8;
insert into user values(1,'wingchi','234567',null,null,true) ;
insert into user values(2,'hk','123456',null,null,true) ;
- 创建dao层,mybatis的知识点:
//UserMapper.java
@Mapper
public interface UserMapper {
public User getUserByName(@Param("name") String name ) ;
}
UserMapper.xml (注意要和java文件同名!)
<?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.wingchi.mapper.UserMapper">
<select id="getUserByName" parameterType="string" resultType="com.wingchi.bean.User">
select * from `user` where name = #{name}
</select>
</mapper>
③、配置Spring Security Config
回到实验的开头铺垫知识,我们需要重写WebSecurityConfigurerAdapter
中的一些方法完成我们的配置
这里贴出我的代码:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new UserService());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() // 禁用csrf
.formLogin() // 开启http表单认证
.and()
.authorizeRequests() // 身份认证请求
.antMatchers("/hello/**").permitAll()
//任何匹配 /hello/**的路径,允许所有人访问
.anyRequest().authenticated(); //其他的所有请求,都需要认证证。
}
}
踩坑记录🙋♀️: 我在配置的时候,因为不知道有http认证这个知识点,既没有配置 httpBasic()方式,也没有配置 formLogin()方式,这样是没办法正常运行的。 了解http的几种认证方式可以参考👉
这里
④、编写controller
@Controller
public class UserController {
@RequestMapping("/user")
@ResponseBody
public String user(String name ) {
return "hello"+name;
}
@RequestMapping("/hello")
@ResponseBody
public String hello() {
return "hello";
}
}
⑤、登录加密
从spring security5开始,就要求必须手动配置密码加密方式,spring官方推荐使用BCrypt加密,并明确指出sha和md5都是不安全的。所以这里使用BCrypt方式
配置十分简单:
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
spring security会自动应用这个bean。
参考:
https://blog.csdn.net/itguangit/article/details/78928886
https://blog.csdn.net/wangb_java/article/details/86676166
《spring security实战–陈木鑫》