一、 基本配置
配置在系统中使用SpringSecurity,需要在pom.xml中加入spring-boot-starter-security依赖
代码如下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
然后新建一个类继承org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter类,用于实现SpringSecurity配置
package com.client;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class TestSecurity extends WebSecurityConfigurerAdapter{
}
这样在我们项目的中就启用了最基本的Security配置,这时启动时会在控制台中打印当前用户的密码信息。如:
Using generated security password: c0b0604b-650c-42a1-b430-566dc0a6b77c
这是我这里打印的,这时当我们访问相关的接口时会自动跳转到如下界面:
输入用户名:user 和控制台输出的密码即可登录,我这里的密码为:c0b0604b-650c-42a1-b430-566dc0a6b77c
登录后就可以自由访问相关的接口信息了
另外由于SpringSecurity会将用户信息存储到Cookie和SecurityContextHolder对象中因此我们在登出里需要消除Cookie信息和SecurityContextHolder信息,当然我们也可以在任意地方通过SecurityContextHolder.getContext().getAuthentication().getPrincipal()取得当前登录的用户信息。 如:
Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
org.springframework.security.core.userdetails.User user = (org.springframework.security.core.userdetails.User)obj;
System.out.println(user.getPassword());
System.out.println(user.getUsername());
System.out.println(user.getAuthorities());
二、自定义配置
在很多情况下我们需要自定义自己的用户名密码和访问权限信息,这时我们就需要对前面说过的org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter类进行复写相关的接口用于我们自己的信息
配置1:自定义用户信息和密码信息
由于SpringSecurity要求密码必须进行加密,因此我们需要先设置一个加密方式,设置方式有两种
第一种,直接注入一个加密方式,如下面代码中,即注入了密码加密方式,并设置用户为admin,密码为admin,同时admin具有admin角色:
package com.client;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class TestSecurity extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password(new BCryptPasswordEncoder().encode("admin")).roles("admin");
}
}
第二种方式即直接在代码中设置密码加密方式, 下面的代码实现的功能和上面的代码实现功能相同
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("admin")).roles("admin");
}
在没有给角色设置任何权限的情况下,默认为角色拥有所有权限,如以下代码,角色admin和角色user权限相同
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("admin")).roles("admin")
.and().withUser("user").password(new BCryptPasswordEncoder().encode("user")).roles("user");
}
配置2:给自定义角色设置权限信息
既然我们给用户设置了角色,那么我们就需要给对应的角色配置相关的权限,如可以访问哪个接口不能访问哪个接口等。设置角色我们需要复写另外一个configure方法。代码如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/test/**").hasRole("user").antMatchers("/**").hasRole("admin")
.anyRequest().authenticated().and().formLogin().and().httpBasic();
}
在这个代码中配置了user角色只能访问 test路径下面的所有接口,而admin可以访问所有接口;
注意:这里有一个顺序的问题,因此/test/**包含的/**内,所以一定先配置范围小的,然后再配置范围大,否则就会造成配置失效的问题,另外也不能配置重复路由。如/test/**已经配置了,如果再配置一个同样也会造成无法访问的问题
在这个代码中由于/test/**这个权限给user和test都进行了配置,所有会造成user角色的无效,而test角色有效。因此要尽量避免这种问题的发生。如果需要配置多个角色可以用hasAnyRole方式配置多个角色,代码如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/test/**").hasAnyRole("admin","user","test")
.antMatchers("/**").hasRole("admin")
.anyRequest().authenticated().and().formLogin().and().httpBasic();
}
过滤请求配置
有一些接口我们如果不想进行权限认证可以通过下面的方式设置。如:下面的代码即访问/test/show接口时不需要进行用户认证
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/test/show");
}
自定义用户认证配置
上面的用户登录功能在我们的真正的项目中使用是比较少的。因此不论是什么样的系统,用户的信息一般都是在数据库中进行存储着,这样我们就需要通过数据来进行用户及相关密码的验证,因此我们需要将用户的验证信息改为数据库验证。在这里我们需要用到org.springframework.security.core.userdetails.UserDetailsService类。
第一步,我先创建一个类,继承org.springframework.security.core.userdetails.UserDetailsService类并复写loadUserByUsername方法。代码如下:
package com.client;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserDetailsServiceImpl implements UserDetailsService{
@Autowired
private PasswordEncoder encode;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
}
在这个方法中,username即是当前登录用户。而encode则是密码的加密算法
第二步:设置当前用户的信息用于验证登录。修改上面的代码,查询数据库取得用户信息及权限信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
//用户的角色信息 通过数据库查询获得,我这里做一个角色模拟,就不再进行数据库查询了
authList.add(new SimpleGrantedAuthority("ROLE_user"));
authList.add(new SimpleGrantedAuthority("ROLE_admin"));
//根据用户的登录名到数据库中查询相关的用户信息;我这里模拟一个用户,就不做数据库查询了 new User("admin", encode.encode("123456"),authList);
return new User("admin", encode.encode("123456"),authList);
}
第三步:修改Security配置信息,这里是修改上面已经写了的TestSecurity类 。即需要设置用户的处理服务。 代码如下:
package com.client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class TestSecurity extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl impl;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(impl);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.authorizeRequests().antMatchers("/test/**").hasAnyRole("admin", "user", "test")
.antMatchers("/**").hasRole("admin")
.anyRequest().authenticated().and().httpBasic();
}
@Override
public void configure(WebSecurity web) throws Exception {
}
}
三、http.authorizeRequests()重点参数说明
1、anyRequest 匹配所有的URL;如代码中的.anyRequest().authenticated() 即所有请求都需要进行登录认证
2、formLogin() 登录表单配置,设置formLogin后,才会显示对应的登录界面,否则会弹出对应的对话框要求输入用户名密码。
3、httpBasic() 认证方式
4、.permitAll() 不需要认证即可以访问的资源设置
5、withObjectPostProcessor() 后置处理程序
四、WebSecurityConfigurerAdapter类说明
这个类的全名为:org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter。我们可以通过复写这个类里面的相关方法,来对权限认识对应相关的操作,这个类里面可以复写的主要方法有下面几个:
protected void configure(AuthenticationManagerBuilder auth) throws Exception;
public void configure(WebSecurity web) throws Exception;
protected void configure(HttpSecurity http) throws Exception;
public AuthenticationManager authenticationManagerBean() throws Exception;
protected AuthenticationManager authenticationManager() throws Exception;
public UserDetailsService userDetailsServiceBean() throws Exception
protected UserDetailsService userDetailsService();
其中 AuthenticationManagerBuilder 主要是用于创建AuthenticationManager(认证管理器),可以通过AuthenticationManager进行内存身份验证、LDAP身份验证、基于数据库的身份验证等,AuthenticationManagerBuilder 也可以设置当前系统的用户信息或增加UserDetailsService、AuthenticationProvider等。。如以下代码就是设置当前的内存用户信息
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password(new BCryptPasswordEncoder().encode("admin")).roles("admin")
.and().withUser("user").password(new BCryptPasswordEncoder().encode("user")).roles("user")
.and().withUser("test").password(new BCryptPasswordEncoder().encode("test")).roles("test");
}
AuthenticationManagerBuilder中的authenticationEventPublisher方法则是定义了Spring Security中用户授权成功或失败的通知机制。它接收一个AuthenticationEventPublisher接口。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationEventPublisher(new AuthenticationEventPublisher() {
@Override
public void publishAuthenticationSuccess(Authentication authentication) {
// 成功后的通知
System.out.println("success");
}
@Override
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
// 失败后的通知
System.out.println("failure");
}
});
}
AuthenticationManagerBuilder中的authenticationProvider方法则是定义了Spring Security中的认证机制。它接收一个AuthenticationProvider接口,通过这个接口来自定义登录信息。这个接口中提供了两个方法。其中supports方法用于判断支持哪个验证,authenticate方法用于执行验证。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new AuthenticationProvider() {
//支持哪种验证
@Override
public boolean supports(Class<?> authentication) {
//如果是支持用户名密码的验证可以这样写
return authentication.equals(UsernamePasswordAuthenticationToken.class);
// return false;
}
//验证方式
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//取得密码
String pwd = authentication.getCredentials().toString();
//取得用户名
String uname = authentication.getPrincipal().toString();
if(/*自定义验证通过-如进行数据库验证或LDAP验证等*/true) {
return new UsernamePasswordAuthenticationToken(uname, pwd);
}
//如果验证不通过
return null;
}
});
}
Spring Security中进行身份验证的是AuthenticationManager接口,ProviderManager是它的一个默认实现,但它并不用来处理身份认证,而是委托给配置好的AuthenticationProvider,每个AuthenticationProvider会轮流检查身份认证。检查后如果成功返回Authentication对象失败则抛出异常。而验证身份其实就是调用userDetailsService。因此我们进行身份认证时多采用实现UserDetailsService接口的方式来进行处理。这个接口需要返回一个用户信息对象UserDetails,里面包含用户名密码及权限信息。AuthenticationProvider会通过UserDetails这个对象和Authentication对象进行比较验证。
WebSecurity
WebSecurity
是Spring Security
的一个SecurityBuilder
。它的任务是基于一组WebSecurityConfigurer
构建出一个Servlet Filter
,具体来讲就是构建一个Spring Security
的FilterChainProxy
实例。这个FilterChainProxy
实现了Filter
接口,也是通常我们所说的Spring Security Filter Chain
,所构建的FilterChainProxy
实例缺省会使用名称springSecurityFilterChain
作为bean
注册到容器,运行时处理web
请求过程中会使用该bean
进行安全控制。每个FilterChainProxy
包装了一个HttpFirewall
和若干个SecurityFilterChain
, 这里每个 SecurityFilterChain
要么对应于一个要忽略安全控制的URL
通配符(RequestMatcher
);要么对应于一个要进行安全控制的URL
通配符(HttpSecurity
)
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/test/sss");
}
HttpSecurity
HttpSecurity是主要是权限认证的信息配置。其中openidLogin()用于配置基于openId的认证,headers()用于将安全标头添加到响应,cors()用于配置跨域资源共享,sessionManagement()用于会话管理,portMapper()允许配置一个PortMapper,其他提供SecurityConfigure的对象使用PortMapper从Http重定向到Https或从Https重定向到Http。默认情况下,Spring Security使用一个PortMapperImpl映射Http端口8080到Https端口8443,Http端口80到Https商品443。
jee()配置基于容器的认领,这种情况下认证由Sevlet容器管理。
x509()配置基于x509的认证。
rememberMe()用于配置记住我的认证。
authorizeRequests()基于HttpServletRequests的访问限制认证。
requestCache() 允许配置请求缓存
exceptionHandling() 允许配置错误处理
securityContext() 在HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用
servletApi() 将HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用
csrf() 添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用
logout() 添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success
anonymous() 允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”
formLogin() 指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面
oauth2Login() 根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证
requiresChannel() 配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射
httpBasic() 配置 Http Basic 验证
addFilter(filter) 添加过滤器
在HttpSecurity中authenticated认证会有权限的认证,而permitAll则没有权限的认证,只需要用户登录了即可,可以通过permitAll来过滤一般的静态界面请求。