说明
security会对服务端的接口添加权限过滤,不具备权限的请求将被拒绝。
引入security
要引入security,只需添加依赖即可:
在pom.xml中引入security:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
运行工程,控制台会生成一个随机的密码:
当用浏览器访问服务端接口时,会提示输入用户名和密码。用户名为user,密码即为生成的随机密码。该验证方式为httpSecurity basic
验证。
若要直接调用接口访问,则需要在请求头的Authorization
中附加用户名/密码。
使用postman时,需要将请求的Authorization
属性的TYPE设置为Basic Auth。
若要关闭该验证功能,则为Application启动类的@SpringBootApplication
注解添加排除类SecurityAutoConfiguration.class
:
@SpringBootApplication(exclude=SecurityAutoConfiguration.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
但该方式的优先级低于配置类。若配置类设置了开启验证,则依然还是需要进行验证。
设置
设置用户名和密码
在application.properties中添加属性:
spring.security.user.name=user
spring.security.user.password=123
这样,就将security验证的用户名和密码修改为user
和123
。但这样意味着登录系统的用户名和密码是固定的,不推荐。
若使用配置类,则可以不用在application.properties中添加属性,而是直接在配置类中设置。
配置类优先级更高。若配置类中没有设置用户名和密码,则使用application.properties中的设置。
使用配置类
添加一个配置类:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 设置密码验证方式
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 设置对各个接口的访问过滤
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests() // 对请求进行授权
.antMatchers("/login","/version").permitAll() // 设置不需要认证的请求
.antMatchers("/test").hasAuthority("p1") // 设置/test接口需要p1权限
.anyRequest().authenticated() // 设置所有请求都需要认证
.and() // 上一项配置已经结束,下一项配置将开始
.httpBasic(); // 开启httpBasic登录
httpSecurity.csrf().disable(); // 关闭默认csrf认证
}
// 设置对静态资源的访问过滤
@Override
public void configure(WebSecurity web) throws Exception {
// 设置不拦截的静态资源
web.ignoring().antMatchers("/file/**","/images/**");
}
}
其中:
@EnableWebSecurity
:开启Security功能,从而支持Security的注解。- 若密码不需要使用任何加密,则
passwordEncoder()
的返回可设置为NoOpPasswordEncoder.getInstance()
。 antMatchers()
:可传入多个String
参数,用于匹配多个请求API。结合permitAll()
,可同时对多个请求设置忽略认证。and()
:用于表示上一项配置已经结束,下一项配置将开始。
然后创建一个Service文件用于Security查询用户信息:
@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Autowired
UserDao userDao;
// 根据账号查询用户信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDto userDto = userDao.getUserByUsername(username);
if(userDto == null){
// 如果用户查不到,返回null,由provider来抛出异常
return null;
}
// 根据用户的id查询用户的权限
List<String> permissions = userDao.findPermissionsByUserId(userDto.getId());
//将permissions转成数组
String[] permissionArray = new String[permissions.size()];
permissions.toArray(permissionArray);
UserDetails userDetails = User.withUsername(userDto.getUsername()).password(userDto.getPassword()).authorities(permissionArray).build();
return userDetails;
}
}
当用户请求时,Security便会拦截请求,取出其中的username字段,从Service中查询该账户,并将信息填充到一个userDetails
对象中返回。这样Security便拿到了填充后的userDetails
对象,也就是获取到了包括权限在内的用户信息。
验证
Security默认开启session。
配置好之后,当用户请求时,会要求先进行登录。若登陆成功,就会返回给用户一个session。
之后当用户访问接口时,每次都会对请求进行拦截过滤,取出session进行查询判断,验证是否具有接口的访问权限。
若有权限,则返回正常结果;否则返回403错误。
退出登录
由于使用session,因此退出登录需要将请求中的session清除:
@RequestMapping("/logout")
public String logout(HttpSession session){
session.invalidate();
return "退出成功";
}
然后设置httpSecurity.logout().logoutUrl("/logout")
,注意logoutUrl需要设置为自定义的接口。
WebSecurityConfigurerAdapter.configure(HttpSecurity httpSecurity)
相关
配置文件中,configure(HttpSecurity httpSecurity)
用于定义一系列的过滤规则。
顺序优先级
整个http.xxx的校验是按照配置的顺序从上往下进行。若某个条件已匹配过,则后续同条件的匹配将不再被执行。也就是说,若多条规则是相互矛盾的,则只有第一条规则生效。典型地:
// 只有具有权限a的用户才能访问/get/resource
httpSecurity.authorizeRequests()
.antMatchers("/get/resource").hasAuthority("a")
.anyRequest().permitAll();
// 所有用户都能访问/get/resource
httpSecurity.authorizeRequests()
.anyRequest().permitAll()
.antMatchers("/get/resource").hasAuthority("a");
但若是包含关系,则需要将细粒度的规则放在前面:
// /get/resource需要权限a,除此之外所有的/get/**都可以随意访问
httpSecurity.authorizeRequests()
.antMatchers("/get/resource").hasAuthority("a")
.antMatchers("/get/**").permitAll();
授权方式
授权有两种方式:
- web授权(基于请求url),在
configure(HttpSecurity httpSecurity)
中设置。 - 方法授权(在方法上使用注解标明授权)。
web授权
@Override
httpSecurity.authorizeRequests() // 对请求进行授权
.antMatchers("/test1").hasAuthority("p1") // 设置/test1接口需要p1权限
.antMatchers("/test2").hasAnyRole("admin", "manager") // 设置/test2接口需要admin或manager角色
.and() // 上一项配置已经结束,下一项配置将开始
.httpBasic(); // 开启httpBasic登录
其中:
hasAuthority("p1")
表示需要p1权限hasAnyAuthority("p1", "p2")
表示p1或p2权限皆可
同理:
hasRole("p1")
表示需要p1权限hasAnyRole("p1", "p2")
表示p1或p2权限皆可
方法授权
@PreAuthorize("hasAuthority('p')")// 拥有p权限才可以访问
public String getResource(){
return "访问资源";
}
在其中可以使用表达式:
@PreAuthorize("#user.name.equals('admin')")
public void getResource(User user) {
System.out.println("只有管理员才能获取到的数据");
}
也可以基于角色:
@PreAuthorize("hasAnyRole('admin', 'manager')")
public void getResource(User user) {
System.out.println("只有管理者才能获取到的数据");
}
表单登录
访问接口时,默认会打开一个登录页面,这就是表单登录。
httpSecurity.authorizeRequests()
.and()
.formLogin()//允许表单登录
.successForwardUrl("/login-success");//自定义登录成功的页面地址
其中formLogin()
表示允许表单登录。当调用了该方法,即会显示登录页面。
表单登录默认开启。若重载configure(HttpSecurity httpSecurity)
且在其中没有调用formLogin()
,则表单登录会关闭。此时可以启用httpBasic
:
@Override
httpSecurity.httpBasic(); // 开启httpBasic登录
这样浏览器访问时会弹窗提示输入用户名和密码。
若不需要表单登录,那么不附加formLogin()
和httpBasic()
。
登出
httpSecurity.logoutUrl()
和httpSecurity.addLogoutHandler()
是冲突的,通常只用其中之一。
对于使用token且服务端不进行存储的情况,不需要请求服务端进行登出,直接由前端将token丢弃即可。
跨域
要解决跨域问题,通常需要:
httpSecurity.cors()
.and()
.csrf().disable();
其中:
cors()
允许跨域资源访问。csrf().disable()
禁用跨域安全验证。
session创建规则
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
:指的是设置session的创建策略。包含4个值:
- ALWAYS:总是使用session。若没有session则创建。
- NEVER:不创建session。但若应用中有其他地方创建了session,将使用。即用户登录时将不创建session。
- IF_REQUIRED:默认值。如果有必要,则创建session。也就是用户登录成功时为其创建一个session。
- STATELESS:不创建session,也不使用session。
Security默认使用的的策略为IF_REQUIRED
。若使用token的方式,意味着不需要session,那么就需要设为STATELESS
。
自定义过滤器
可基于Security提供的抽象类Filter创建自定义Filter,然后调用HttpSecurity
的成员方法来添加到filterChain中,例如:
定义Filter:
public class MyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
然后添加到filterChain:
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 自定义token过滤器
httpSecurity.addFilterBefore(new MyFilter(), UsernamePasswordAuthenticationFilter.class)
}
添加的方法除了addFilterBefore()
,还有addFilter()
、addFilterAfter()
、addFilterAt()
等。