springsecurity简单用过后的一些笔记

 

Spring Security认证是由 AuthenticationManager 来管理的,但是真正进行认证的是 AuthenticationManager 中定义的 AuthenticationProvider。AuthenticationManager 中可以定义有多个 AuthenticationProvider。当我们使用 authentication-provider 元素来定义一个 AuthenticationProvider 时,如果没有指定对应关联的 AuthenticationProvider 对象,Spring Security 默认会使用 DaoAuthenticationProvider。DaoAuthenticationProvider 在进行认证的时候需要一个 UserDetailsService 来获取用户的信息 UserDetails,其中包括用户名、密码和所拥有的权限等。所以如果我们需要改变认证的方式,我们可以实现自己的 AuthenticationProvider;如果需要改变认证的用户信息来源,我们可以实现 UserDetailsService。

 

 

1.实现UserDetailsService 接口

CustomUserDetailsService类: 

package cn.lger.security;


import cn.lger.dao.UserDao;
import cn.lger.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.ArrayList;
import java.util.List;

public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private  UserDao userDao;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userDao.findByUsername(username);
        if(user == null){
            throw new UsernameNotFoundException("not found");
        }
        List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority(user.getRole()));
        System.err.println("username is " + username + ", " + user.getRole());
        return new org.springframework.security.core.userdetails.User(user.getUsername(),
                user.getPassword(), authorities);
    }

}

在下面的configure(AuthenticationManagerBuilder auth) 方法中添加这个UserDetailsService 

SecurityConfig类:

package cn.lger.config;

import cn.lger.security.CustomUserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        return new CustomUserDetailsService();
    }


    @Override  
    protected void configure(AuthenticationManagerBuilder auth)  
            throws Exception {  
        auth
                .userDetailsService(userDetailsService())
                .passwordEncoder(new BCryptPasswordEncoder());
    }  
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .antMatchers("/css/**","/js/**","/img/**","/vendors/**").permitAll()
                .anyRequest().permitAll()
                .and()
            .formLogin()
                .defaultSuccessUrl("/user/list")
                .permitAll()
                .and()
            .logout()
                .permitAll()
                .and()
                .csrf().disable();
    }
  
}

这里虽然是给AuthenticationManagerBuilder这个构造类来添加的UserDetailsService 而不是真正的AuthenticationManager这个类,但是经过我尝试过断点调试,在启动阶段就会进入断点,断点的调试结果如下:

刚开始进入我们自己写的安全配置类SecurityConfig的父类WebSecurityConfigurerAdapter的init(final WebSecurity web)这个方法

断点的位置会调用getHttp()这个方法,然后我们下一步进入这个方法的内部看看,如下图:

这里断点位置我已经看到AuthenticationManager这个类的一个对象的构建了,然后继续进入authenticationManager()这个方法内部看看,这个AuthenticationManager对象是如何构建的:

进来先判断AuthenticationManager这个类是不是已经初始化一个对象了了,如果初始化了就直接返回AuthenticationManager的对象,否则就调用configure(AuthenticationManagerBuilder auth)这个方法,因为SecurityConfig这个类重写了WebSecurityConfigurerAdapter这个类原本的configure(AuthenticationManagerBuilder auth)的这个方法,所以运行到下一个断点会到SecurityConfig类的configure(AuthenticationManagerBuilder auth)方法上:

这样就在构建AuthenticationManager类的实例前给它提供了一个UserDetailsService实例,这样DaoAuthenticationProvider 就可以通过这个实例里面的loadUserByUsername(String username)(这个方法需要我们自己实现)获取一个UserDetails的实例,并且我实验过,这个loadUserByUsername(String username)方法会在我们登陆认证的时候起作用,若果我们登陆成功就会返回一个UserDetails的实例,这个UserDetails的实例中包含了用户的用户名,密码和权限(权限是一个set(集合),说明可以支持一个用户多个角色)

 

下面我说一下springsecurity中静态资源加载时我遇到的问题和解决方案

首先用了springsecurity我们可以继承WebSecurityConfigurerAdapter这个类,然后个根据个人需要来重写这个类的方法,下面的例子我用SecurityConfig这个类来继承它,如果重写了configure(HttpSecurity http)这个方法,下面的第二句代码是拦截所有的请求,所以我们请求的静态资源也会同样被拦截,所以我们需要告诉springsecurity我的静态资源是可以放行的,这个时候就需要用到红色框中的第一行代码来实现对静态资源放行,我们不需要在路径前面加/static,因为默认情况下它会认为这些路径下的文件,这几个路径分别是:“classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"(这个我会面会说为什么)

 

这里我对刚才说下为什么默认情况下springsecurity认为静态资源会在这几个目录下(“classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/"),这个也是有理有据的,不是我瞎掰的,其实我本来也不清楚原因,都是网上说的,后来我的一个好基友告诉我的我才知道,这里就谢谢这位好基友,好了说了这么多,现在就看看哪里指定了这些路径,我们点开application.properties这个文件在上面输入如下图一样的配置

然后按着Ctrl键点击这个配置,然后会进入下面的这个类

根据图中的标记就看得出来,为什么默认情况下springsecurity会认为静态资源在这几个目录下了吧。

 

 

除了上面的那种方式外还有一种可以放行静态资源的方法,不过这个方法还是需要继承WebSecurityConfigurerAdapter这个类,这次不需要重写configure(HttpSecurity http)这个方法,原因是虽然还是要有拦截所有请求这几代码,但是WebSecurityConfigurerAdapter这个类中的configure(HttpSecurity http)这个类中原本就有着句代码,如下图

但是我们这次需要重写configure(WebSecurity web)这个方法来实现对静态资源的放行,具体的操作如下图:

 那么着两种方式的配置有什么区别呢,这次自能是个人感觉了,毕竟技术有限,有些东西证明不了,只能猜想,那么我感觉第一种方式的话是需要经过拦截器的拦截,然后再根据我们的配置,看到需要放行才放行,就是这个过程还是需要经过拦截器的,但是第二种方法看英文就知道(ignoring忽视的意识),就是这个拦截器直接忽视这些文件夹下的资源(默认情况下还是上面说的那几个路径“classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/"下的文件夹)所以它们的区别是一个经过拦截器一个不经过拦截器。

 

这里需要注意的是如果继承了WebMvcConfigurationSupport这个类,并且将在子类上面添加了@Configuration(将这个类看着配置类),那么上面说的默认情况下资源文件的路径在(“classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/"“classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/")这就几个目录下的情况会无效,我的好基友和我说是启动了这个配置类后就在application.properties文件下配置的spring.resources.static-locations不起作用了,具体为什么不起作用了英文及技术问题我也无法证明,感觉是WebMvcConfigurationSupport这个类内部有方法影响所造成的。下面的例子我用ResourcesConfig这个类来继承WebMvcConfigurationSupport这个并且在类的上面加了@Configuration这个注解,声明它为配置类:代码如下

然后我在SecurityConfig类中用第二种配置方式,代码如下:

结果访问页面的时候所有的静态资源都加载不到,状态码都是404(找不到资源路径)

如果把ResourcesConfig类上的@Configuration这个注解去掉就可以正常的找到这些静态资源文件,所以我就觉得就是这个父类里面的方法影响的问题导致不会去默认的那几个路径下找资源文件

去掉@Configuration这个注解后的结果如下:

 

 

 

如果你你使用ResourcesConfig类继承WebMvcConfigurationSupport这个类并且在类的上面加了个@Configuration注解,那么就不会去上面说的默认路径下找静态资源文件,所以我们需要自己告诉它去哪里找这些静态资源,这里我告诉它要去classpath:/static/下面去找这些资源文件,代码如下:

这样就告诉它所有的静态资源/**都在classpath:/static/下面找

 

我下面对这个上面的代码做了简单的代码分析

在WebMvcConfigurationSupport中有个@Bean的方法,该方法具体如下:

红框里面的第一行代码上面addResourceHandlers(registry)在父类里面是一个空函数,因为我们继承了WebMvcConfigurationSupport这个父类并且重写了这个方法,所以这里会调用我们的子类里面写的方法

下面看看父类中的addResourceHandlers(registry)这个空方法:

而我们重写了该方法后是这样的,重点在红框中的那段代码,代码如下:

按着Ctrl点击addResourceHandler("/**")这段,然后会跳转到WebMvcConfigurationSupport中的这段代码中:

这里我们创建了一个ResourceHandlerRegistration(资源处理定位器)实例,然后将这个ResourceHandlerRegistration添加到registrations(一个List<ResourceHandlerRegistration>),注意这里返回的是一个ResourceHandlerRegistration实例的引用,所以addResourceLocations("classpath:/static/")是ResourceHandlerRegistration这个类中的一个方法,下面看看这个方法到底是干嘛的,还是按着Ctrl点击这个方法,点击去会看到:

这个方法只是将传过来的多个字符串存到locationValues这个字符串列表里面

 

第一句代码可能会有点绕晕,这里主要记住那个添加了@Bean注解的HandlerMapping resourceHandlerMapping()方法中我们创建了一个ResourceHandlerRegistry类的一个实例,然后调用了我们自己写的ResourcesConfig类(继承WebMvcConfigurationSupport类并重写protected void addResourceHandlers(ResourceHandlerRegistry registry)方法)的protected void addResourceHandlers(ResourceHandlerRegistry registry)方法,方法里面我们为ResourceHandlerRegistry类的实例中的registrations(一个ResourceHandlerRegistration列表)添加了一个ResourceHandlerRegistration类实例,并且这个为ResourceHandlerRegistration类实例中的locationValues赋值

 

第二句代码的是为了获取一个HandlerMapping的实例,按着Ctrl点击进去看看registry.getHandlerMapping()这个方法内部是怎么样的,里面的代码如下:

这里面有两个for循环,第一个for循环遍历registry中的ResourceHandlerRegistration列表registrations,而记得我们上面第一句代码内部实现就有给这个registrations初始化,所以里面放置了一ResourceHandlerRegistration实例registration,而在创建这个ResourceHandlerRegistration实例的时候我们给他传入了多个String参数,这些参数就是我们请求静态资源的时候可能出现的路径(比如http://localhost:8080/js/test.js,http://localhost:8080/css/style.css在8080后面的这一段我们可以用/**请求时表示所有的静态资源)因为在ResourceHandlerRegistration类中这多个String 参数是以String 数组的方式存起来,所以我们在这里可以获取这个String 数组并且遍历这个数组,然后看registration.getRequestHandler()这句的内部,主要看红色框中的那句话,给handler的locationValues属性赋值。

 

 然后看看第三句handler.afterPropertiesSet(),如果点看里面看具体的代码比较复杂,所以我就只是看看执行后里面的变化

 

执行后是这样的:

 执行前locations这个Resource列表长度为0,resourceResolvers这个ResourceResolver(资源解析器)的列表也是长度为0,执行后这两个列表都有了元素,有多少个元素这个根据情况来决定(这个我也说的不是很清楚,可以改变

红框中的参数的个数来改变列表中的元素个数,具体情况需要看handler.afterPropertiesSet()这个方法内部是怎么实现的。

 

 

然后四句就是把请求的路径/**(端口后面的所有静态资源请求)和这个handler用map的键值对方式对应起来,最后两句就是把我们最后的urlMap放入这个handlerMapping中,然后返回给上一层

具体过程大概是这样的,这里面有写说不清楚的可能是因为我本人的语文不好,组织能力有点难,还有可能理解错误了,或者湖忽略了某些细节,如果有什么问题或者纠正的,请在评论中告诉我,让我和给位学习学习!!

posted @ 2018-04-11 11:14  183区展伯  阅读(285)  评论(0编辑  收藏  举报