Spring Security认证是由 AuthenticationManager 来管理的,可是真正进行认证的是 AuthenticationManager 中定义的 AuthenticationProvider。AuthenticationManager 中能够定义有多个 AuthenticationProvider。当咱们使用 authentication-provider 元素来定义一个 AuthenticationProvider 时,若是没有指定对应关联的 AuthenticationProvider 对象,Spring Security 默认会使用 DaoAuthenticationProvider。DaoAuthenticationProvider 在进行认证的时候须要一个 UserDetailsService 来获取用户的信息 UserDetails,其中包括用户名、密码和所拥有的权限等。因此若是咱们须要改变认证的方式,咱们能够实现本身的 AuthenticationProvider;若是须要改变认证的用户信息来源,咱们能够实现 UserDetailsService。css
1.实现UserDetailsService 接口java
CustomUserDetailsService类: web
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 spring
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)这个方法app
断点的位置会调用getHttp()这个方法,而后咱们下一步进入这个方法的内部看看,以下图:ide
这里断点位置我已经看到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中,而后返回给上一层
具体过程大概是这样的,这里面有写说不清楚的多是由于我本人的语文很差,组织能力有点难,还有可能理解错误了,或者湖忽略了某些细节,若是有什么问题或者纠正的,请在评论中告诉我,让我和给位学习学习!!