SpringSecurity入门
基础
- spring security的底层就是一个过滤器链
 - ExceptionTranslationFilter是一个异常过滤器,用来处理认证授权过程中的异常
 - UseranmePasswordAuthenticationFilter,拦截/login的post请求,即认证时校验用户名、密码
 - spring boot整合spring security会自动化配置,即引入security依赖即可使用,不需要我们配置过滤器等
 - 认证,可理解为登录时的验证,当我们登录时就需要从数据库中查询用户名和密码,使用security只需实现
UserDetailsService接口,在自定义的实现类中进行查询操作;之后返回一个User对象,这个对象是security为我们提供的,这个对象的属性包括查询到的用户名、密码、权限 - 登录时输入的用户名和密码如何与数据库中的用户名密码比较,我们只需写一个类继承UsernamePasswordAuthenticationFilter,重写attemptAuthentication方法,在当前类中会接收登录时输入的用户名和密码,在attemptAuthentication方法中认证
 
点击查看源码
public class UsernamePasswordAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {
	// ~ Static fields/initializers
	// =====================================================================================
	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
	private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
	private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
	private boolean postOnly = true;
	// ~ Constructors
	// ===================================================================================================
	public UsernamePasswordAuthenticationFilter() {
		super(new AntPathRequestMatcher("/login", "POST"));
	}
	// ~ Methods
	// ========================================================================================================
	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}
		String username = obtainUsername(request);
		String password = obtainPassword(request);
		if (username == null) {
			username = "";
		}
		if (password == null) {
			password = "";
		}
		username = username.trim();
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		return this.getAuthenticationManager().authenticate(authRequest);
	}
}
PasswordEncoder接口用于数据加密,即密码加密
点击查看源码
package org.springframework.security.crypto.password;
public interface PasswordEncoder {
    String encode(CharSequence var1);
    boolean matches(CharSequence var1, String var2);
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}
认证方式
- 新建一个springboot项目,导入security依赖,任意访问一个控制器中的方法都需要认证,用户名为user,密码在控制台
 - 方式一:在配置文件yml中设置密码
 - 方式二:在配置类中设置(继承WebSecurityConfigurerAdapter,重写configure和password方法)
 - 方式三:在UserDetailsService实现类中查询数据库中的用户名和密码,将实现类注入配置类
 
点击查看实现类
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 设置权限
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
        // 返回user对象,参数为模拟从数据库中查询到的用户名、密码
        return new User("admin", new BCryptPasswordEncoder().encode("123456"),auths);
    }
}
点击查看配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }
    @Bean
    PasswordEncoder password() {
        return new BCryptPasswordEncoder();
    }
}
- 在配置类中指定自定义的登录页面,不需要认证就能访问的url
 
点击查看配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    //注入数据源
    @Autowired
    private DataSource dataSource;
    //配置对象
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }
    @Bean
    PasswordEncoder password() {
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 退出登录
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
        // 没有访问权限时跳转的自定义页面
        http.exceptionHandling().accessDeniedPage("/unauth.html");
        // 自定义认证页面
        http.formLogin().loginPage("/login.html")
                // 登录的url
                .loginProcessingUrl("/login")
                // 登录成功后跳转的页面
                .defaultSuccessUrl("/success.html")
                // 登录失败的跳转页面
                .failureUrl("/fail.html");
        // 指定url不需要认证
        http.authorizeRequests().antMatchers("/login.html").permitAll()
                // 其他url需要认证
                .anyRequest().authenticated()
        		.and().rememberMe().tokenRepository(persistentTokenRepository())
                // 设置有效时长,单位秒
                .tokenValiditySeconds(60)
                .userDetailsService(userDetailsService);
        // 关闭csrf防护
        //http.csrf().disable();
    }
}
- 认证业务逻辑:访问控制器方法 -> 实现类中查询 -> 认证通过 -> 执行控制器方法
 
项目案例
授权
- 在配置类中通过hasAuthority方法给指定url设置权限,在业务层实现类中给登录主体赋予权限
 
  .antMatchers("/test/index").hasAuthority("admins")
  .antMatchers("/test/main").hasAuthority("user")
- 在配置类通过hasAnyAuthority方法给指定url设置多个权限,访问时,只要具有其一权限即可访问
 
  .antMatchers("/test/index").hasAnyAuthority("admin,manager")
  // 业务层实体类赋予权限
  AuthorityUtils.commaSeparatedStringToAuthorityList("admin,sale");
- 在配置类中通过hasRole方法给指定url设置权限
 
  .antMatchers("/test/index").hasRole("sale")
- 在配置类中通过hasAnyRole方法给指定url设置多个权限,访问时,只需具有其中一个权限即可访问
 
  .antMatchers("/test/index").hasAnyRole("sale,admin")
  // 在业务层实现类中赋予权限时需加上ROLE_前缀
  AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin,ROLE_sale");
注解方式授权
  // 1. 在启动类或配置类中开启注解功能
  @EnableGlobalMethodSecurity(securedEnabled=true)
  // 2. 在控制器的方法上添加角色权限,需加上ROLE_前缀;用户具有该角色权限时,可以访问该方法
  @Secured({"ROLE_normal","ROLE_admin"})
  // 3. 在业务层实现类中赋予登录用户角色权限
  .antMatchers("/test/index").hasAnyRole("sale,admin")
  // 2. 在控制器上添加角色权限,可使用hasAuthority、hasRole、hasAnyRole等;在进入方法前验证是否具有角色权限
  @PreAuthorize("hasAnyAuthority('admins,manager')")
  // 3. 在业务层实现类中赋予登录用户角色权限
  // 2. 在控制器上添加角色权限,可使用hasAuthority、hasRole、hasAnyRole等;在方法执行完验证是否具有角色权限;当我们访问这个方法,执行完之后没有权限时则跳转到403(没有权限)页面
  @PostAuthorize("hasAnyAuthority('admins,manager')")
  // 3. 在业务层实现类中赋予登录用户角色权限
认证业务逻辑
- 自定义一个认证过滤器
TokenLoginFilter继承UsernamePasswordAuthenticationFilter,登录时输入用户名和密码,进入认证过滤器TokenLoginFilter,获取登陆时的用户名和密码,进入UserDetailsServiceImpl实现类,该实现类实现了UserDetailsService接口,在UserDetailsServiceImpl中根据用户名去数据库查询用户信息,返回securityUser对象,认证成功后进入认证过滤器中的successfulAuthentication方法 
源码分析
- 自定义的认证过滤器
TokenLoginFilter继承UsernamePasswordAuthenticationFilter,UsernamePasswordAuthenticationFilter继承了AbstractAuthenticationProcessingFilter,在该类中的doFilter方法会判断该请求是否是post请求,不是则放行,是post则拦截认证 - 之后在
UsernamePasswordAuthenticationFilter类中的attemptAuthentication方法会获取表单提交的数据,然后进行认证(查数据库,比较),认证通过后,将用户数据封装到Authentication 
点击查看源码
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
      if (this.postOnly && !request.getMethod().equals("POST")) {
          throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
      } else {
          String username = this.obtainUsername(request);
          username = username != null ? username : "";
          username = username.trim();
          String password = this.obtainPassword(request);
          password = password != null ? password : "";
          UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
          this.setDetails(request, authRequest);
          return this.getAuthenticationManager().authenticate(authRequest);
      }
  }
- 认证成功后在
AbstractAuthenticationProcessingFilter类中会将数据存入session - 认证失败时异常过滤器
ExceptionTranslationFilter会抛出异常,执行unsuccessfulAuthentication方法;认证成功时则执行successfulAuthentication方法 
attemptAuthentication方法源码分析
- 判断是否是post提交,不是则抛出异常,是post则继续执行
 - 获取表单中提交的数据
 - new一个UsernamePasswordAuthenticationToken对象,将表单提交的数据构建进该对象,并标记为未认证状态
 
  UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
- 调用authenticate方法进行认证,该方法会调用
UserDetailsService的实现类查数据库进行认证 
UsernamePasswordAuthenticationToken对象构建源码分析
UsernamePasswordAuthenticationToken继承了一个抽象类AbstractAuthenticationToken- 在
UsernamePasswordAuthenticationToken类中有两个方法,根据传入的参数调用指定的方法,调用UsernamePasswordAuthenticationToken方法表示标记为未认证状态,调用UsernamePasswordAuthenticationToken方法表示将对象标记未已认证状态 
点击查看源码
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = 540L;
    private final Object principal;
    private Object credentials;
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);
    }
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    }
}
- 抽象类
AbstractAuthenticationToken则是实现了Authentication接口,该接口中包含一些用户信息 
点击查看源码
public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
authenticate方法源码分析
- 调用authenticate方法来进行认证,该方法是
AuthenticationManager接口中的方法,该接口的实现类ProviderManager - 登录时表单提交的数据被封装进UsernamePasswordAuthenticationToken对象,然后传入authenticate方法;在
ProviderManager实现类中authenticate方法会将UsernamePasswordAuthenticationToken对象中的信息迭代,之后判断该对象是否是UsernamePasswordAuthenticationToken类型,之后会调用authenticate方法将对象信息传入进行认证 
  result = provider.authenticate(authentication);
- 认证失败抛出异常,认证成功则将查询到的details复制到authentication对象中
 
try {
  result = provider.authenticate(authentication);
          if (result != null) {
        this.copyDetails(authentication, result);
        break;
    }
} catch (InternalAuthenticationServiceException | AccountStatusException var14) {
    this.prepareException(var14, authentication);
    throw var14;
} catch (AuthenticationException var15) {
    lastException = var15;
}
												
											SpringSecurity入门的更多相关文章
- springSecurity入门小demo--配置文件xml的方式
		
本例子只是一个最最最简单的入门demo,重点讲解xml的配置参数的意思和遇到的坑,主要的功能有: 自定义登录页面,错误页面 配置角色 csrf-403报错解决方法(加上一行代码配置就ok) 后台ifr ...
 - SpringSecurity入门demo
		
配置依赖: <properties> <spring.version>4.2.4.RELEASE</spring.version> </properties& ...
 - SpringSecurity入门例子及遇到的问题解决
		
最近学习<Spring 实战>学习到了SpringSecurity,觉得书本上的例子过于复杂,而且不喜欢它基于java配置,更喜欢用xml文件进行配置 于是在极客学院网上学习,感觉挺不错的 ...
 - SpringBoot 安全管理(一)
		
SpringBoot 安全管理(一) 一.springSecurity入门 添加依赖 <dependency> <groupId>org.springframework.boo ...
 - SpringSecurity身份验证基础入门
		
对于没有访问权限的用户需要转到登录表单页面.要实现访问控制的方法多种多样,可以通过Aop.拦截器实现,也可以通过框架实现(如:Apache Shiro.Spring Security). pom.xm ...
 - SpringSecurity 3.2入门(7)自定义权限控制介绍
		
总结Spring Security的使用方法有如下几种: 一种是全部利用配置文件,将用户.权限.资源(url)硬编码在xml文件中. 二种是用户和权限用数据库存储,而资源(url)和权限的对应关系硬编 ...
 - SpringSecurity 3.2入门(2)环境搭建
		
由于目前Spring官方只提供Meven的下载方式,为了能以最快的速度入门使用框架,这里提供百度网盘下载链接. 注:本入门教程默认已经配置成功SpringMVC框架. 1.web.xml配置 < ...
 - SpringSecurity快速入门
		
作者:SingleXu 链接:https://www.jianshu.com/p/8212a559d633 来源:简书 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 简介 Sp ...
 - 【Spring-Security】Re01 入门上手
		
一.所需的组件 SpringBoot项目需要的POM依赖: <dependency> <groupId>org.springframework.boot</groupId ...
 
随机推荐
- Word中的代码怎样语法高亮
			
在平常我们粘贴代码到Word中的时候,经常会遇到代码粘贴到Word中后没有语法高亮,看着很乱很不友好,Word自带的样式---语法使用着也不尽人意, 网上有很多做法可以使得在插入在Word中的代码能够 ...
 - 搭建NFS服务
			
说明:这里是Linux服务综合搭建文章的一部分,本文可以作为单独搭建yum仓库的参考. 注意:这里所有的标题都是根据主要的文章(Linux基础服务搭建综合)的顺序来做的. 如果需要查看相关软件版本和主 ...
 - Python如何将py文件打包成exe
			
安装pyinstaller 打开cmd窗口,输入pip install pyinstaller,命令行输出successfully表示成功. 生成exe文件 一.单个py文件 在py文件目录下,打开c ...
 - At 、Crontabl定时任务
			
之前笔者是在本地写的博客,然后用 windows 定时任务启动写的脚本上传到 Github 上,现在又遇到了 Linux 上的定时任务,项目还要用到 Quartz 定时任务框架 1. 一次性定时任务 ...
 - SaaS技术栈有多复杂?
			
[特别声明:本文基于Tools and Services I Use to Run My SaaS进行修改.] 软件SaaS化由于需要考量架构的各个方面,所以需要的技术栈非常全面. 以一个客户管理Sa ...
 - Mybatis学习笔记-动态SQL
			
概念 根据不同环境生成不同SQL语句,摆脱SQL语句拼接的烦恼[doge] 本质:SQL语句的拼接 环境搭建 搭建数据库 CREATE TABLE `blog`( `id` VARCHAR(50) N ...
 - 最全总结 | 聊聊 Python 数据处理全家桶(PgSQL篇)
			
1. 前言 大家好,我是安果! Python 数据处理全家桶,截止到现在,一共写过 6 篇文章,有兴趣的小伙伴可以去了解一下! 最全总结 | 聊聊 Python 数据处理全家桶(Mysql 篇) 最全 ...
 - 《手把手教你》系列技巧篇(十八)-java+ selenium自动化测试-元素定位大法之By css中卷(详细教程)
			
1.简介 按计划今天宏哥继续讲解倚天剑-css的定位元素的方法:ID属性值定位.其他属性值定位和使用属性值的一部分定位(这个类似xpath的模糊定位). 2.常用定位方法(8种) (1)id(2)na ...
 - docker-01
			
Docker介绍 1 什么是容器? Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级.可移 ...
 - JmoVxia
			
关于我 网名:季末微夏 英文:JmoVxia 签名:路漫漫其修远兮,吾将上下而求索 标签:iOS开发(ma)工程师(nong).技术爱好者 联系我 邮箱:JmoVxia@gmail.com Githu ...