configure(WebSecurity) 通过重载,配置Spring Security的Filter链
configure(HttpSecurity) 通过重载,配置如何通过拦截器保护请求
configure(AuthenticationManagerBuilder) 通过重载,配置user-detail服务
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("password").roles("USER").build());
manager.createUser(User.withUsername("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
/**
* 配置user-detail服务
* @param auth
* @throws Exception
*/
@Override
public void configure(AuthenticationManagerBuilder auth)throws Exception{
//基于数据库的用户存储、认证
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select account,password,true from user where account=?")
.authoritiesByUsernameQuery("select account,role from user where account=?");
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
@Service
public class SecurityUserDetailsService implements UserDetailsService{
@Autowired
private UserDao userDao;
/**
* 校验用户
* @param username
* @return
* @throws UsernameNotFoundException
*/
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.queryByUserName(username);
if(user == null)throw new UsernameNotFoundException("user not found");
return new org.springframework.security.core.userdetails.User(user.getAccount(),user.getPassword(),userDao.getUserGrantedAuthoritys(user.getId()));
}
}
通常我们在存储密码的时候都是进行加密的,spring security默认提供了三种密码存储方式,同时也可以使用自定义的加密方式:
/**
* 配置user-detail服务
* @param auth
* @throws Exception
*/
@Override
public void configure(AuthenticationManagerBuilder auth)throws Exception{
//基于数据库的用户存储、认证
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select account,password,true from user where account=?")
.authoritiesByUsernameQuery("select account,role from user where account=?")
.passwordEncoder(NoOpPasswordEncoder.getInstance());
}
spring security的请求拦截匹配有两种风格,ant风格和正则表达式风格。编码方式是通过重载configure(HttpSecurity)方法实现。
/**
* 拦截请求
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http)throws Exception{
http.authorizeRequests()
.antMatchers("/","/css/**","/js/**").permitAll() //任何人都可以访问
.antMatchers("/admin/**").access("hasRole('ADMIN')") //持有user权限的用户可以访问
.antMatchers("/user/**").hasAuthority("ROLE_USER");
}
保护请求方法
access(String) 如果给定的SpEL表达式计算结果为true,就允许访问 anonymous() 允许匿名用户访问 authenticated() 允许认证过的用户访问 denyAll() 无条件拒绝所有访问 fullyAuthenticated() 如果用户是完整认证的话(不是通过Remember-me功能认证的),就允许访问 hasAnyAuthority(String...) 如果用户具备给定权限中的某一个的话,就允许访问 hasAnyRole(String...) 如果用户具备给定角色中的某一个的话,就允许访问 hasAuthority(String) 如果用户具备给定权限的话,就允许访问 hasIpAddress(String) 如果请求来自给定IP地址的话,就允许访问 hasRole(String) 如果用户具备给定角色的话,就允许访问 not() 对其他访问方法的结果求反 permitAll() 无条件允许访问 rememberMe() 如果用户是通过Remember-me功能认证的,就允许访问
通常我们都是使用http发送数据,这种方式是不安全的。对于敏感信息我们通常都是通过https进行加密发送。spring security对于安全性通道也提供了一种方式。我们可以在配置中添加requiresChannel()方法使url强制使用https。
/**
* 拦截请求
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http)throws Exception{
http.authorizeRequests()
.antMatchers("/","/css/**","/js/**").permitAll() //任何人都可以访问
.antMatchers("/admin/**").access("hasRole('ADMIN')") //持有user权限的用户可以访问
.antMatchers("/user/**").hasAuthority("ROLE_USER")
.and()
.requiresChannel().antMatchers("/admin/info").requiresSecure();
}
- 只要是对“/admin/info”的请求,spring security都认为需要安全性通道,并自动将请求重定向到https上。
- 与之相反,如果有些请求不需要https传送,可以使用requiresInsecure()替代requiresSecure(),将请求声明为始终使用http传送。
防止CSRF
/**
* 拦截请求
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http)throws Exception{
http.authorizeRequests()
.antMatchers("/","/css/**","/js/**").permitAll() //任何人都可以访问
.antMatchers("/admin/**").access("hasRole('ADMIN')") //持有user权限的用户可以访问
.antMatchers("/user/**").hasAuthority("ROLE_USER")
.and().csrf().disable();
}
remember-me功能
/**
* 拦截请求
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http)throws Exception{
http.authorizeRequests()
.antMatchers("/","/css/**","/js/**").permitAll() //任何人都可以访问
.antMatchers("/admin/**").access("hasRole('ADMIN')") //持有user权限的用户可以访问
.antMatchers("/user/**").hasAuthority("ROLE_USER")
.and().rememberMe().key("abc").rememberMeParameter("remember_me").rememberMeCookieName("my-remember-me").tokenValiditySeconds(86400);
}
/**
* 拦截请求
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http)throws Exception{
http.authorizeRequests()
.antMatchers("/","/css/**","/js/**").permitAll() //任何人都可以访问
.antMatchers("/admin/**").access("hasRole('ADMIN')") //持有user权限的用户可以访问
.antMatchers("/user/**").hasAuthority("ROLE_USER")
.and().formLogin()
.loginPage("/login").usernameParameter("username").passwordParameter("password")
.and().exceptionHandling().accessDeniedPage("/loginfail");
}
通过formLogin()方法来设置使用自定义登录页面,loginPage是登录页面地址,accessDeniePage登录失败跳转地址。
与配置登录功能类似,您也有各种选项来进一步定制您的注销需求:
注销后:
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
.logoutUrl("/my/logout")
.logoutSuccessUrl("/my/index")
.logoutSuccessHandler(logoutSuccessHandler)
.invalidateHttpSession(true)
.addLogoutHandler(logoutHandler)
.deleteCookies(cookieNamesToClear)
.and()
...
}
注意:
LogoutHandler实现指示能够参与注销处理的类
LogoutSuccessHandler 被成功注销后调用LogoutFilter,来处理如重定向或转发到相应的目的地。
@Bean
public SpringAuthenticationProvider springAuthenticationProvider() {
return new SpringAuthenticationProvider();
}
@Bean
public SpringDataUserDetailsService springDataUserDetailsService() {
return new SpringDataUserDetailsService();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@EnableWebSecurity
public class MultiHttpSecurityConfig {
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("password").roles("USER").build());
manager.createUser(User.withUsername("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
@Configuration
@Order(1) // 1
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**") // 2
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}
@Configuration // 3
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
}
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}
public interface BankService {
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();
@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
// ... create and return custom MethodSecurityExpressionHandler ...
return expressionHandler;
}
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor() {
public O postProcess(
O fsi) {
fsi.setPublishAuthorizationSuccess(true);
return fsi;
}
});
}
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
- 调用返回的对象getContext()是SecurityContext接口的实例。这是保存在线程本地存储中的对象。