SpringSecurity的基础操作,登录认证,授权认证等

文章目录

  • SpringSecurity
    • 1、SpringSecurity简介
    • 2、第一个SpringSecurity程序
    • 3、UserDetailsService接口
    • 4、 PasswordEncoder接口
    • 5、自定义登录逻辑
    • 6、自定义登录页面
    • 7、自定义登录成功和失败处理器
    • 8、授权配置
    • 9、角色认证
    • 10、记住我
    • 11、SpringSecurity整合thymeleaf
      • 11.1、获取登录信息
      • 11.2、权限判断
      • 11.3、注销配置
    • 12、csrf

SpringSecurity

1、SpringSecurity简介

​ Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作,就是一个授权(Authorization)和用户认证(Authentication)的安全框架。

SpringSecurity 官方文档:https://docs.spring.io/spring-security/reference/getting-spring-security.html

导入依赖:

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-securityartifactId>
dependency>

2、第一个SpringSecurity程序

​ 创建一个初始化的SpringBoot项目,导入对应的依赖(web依赖、springsecurity依赖等),然后直接启动项目,当访问项目的某个资源时,会跳转到一个登陆页面。

SpringSecurity的基础操作,登录认证,授权认证等_第1张图片

这个是springsecurity默认的登录页面,请求项目的资源时,如果没有登录就不会让你进行访问,而初始的登录账户是user,密码是项目启动时打印的,每次启动项目的密码不同。

SpringSecurity的基础操作,登录认证,授权认证等_第2张图片

3、UserDetailsService接口

​ 如果需要自定义一个自己的登录逻辑,我们需要自己创建一个UserDetailsServiceImpl来实现UserDetailsService接口,然后重写loadUserByUsername方法,自定义登录逻辑。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    // loadUserByUsername()  username参数是前端传来的登录账户参数
    // UserDetails 返回参数,也是一个接口,但是返回这个接口的实现类User
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return null;
    }
}
public interface UserDetails extends Serializable {
	// 返回用户的权限集合,但是不能返回一个null
   Collection<? extends GrantedAuthority> getAuthorities();
	// 获取密码
   String getPassword();
 	// 获取账号
   String getUsername();
	// 判断用户账号是否过期
   boolean isAccountNonExpired();
	// 判断用户是否被锁定
   boolean isAccountNonLocked();
	// 判断用户密码是否过期
   boolean isCredentialsNonExpired();
	// 判断账户是否可用
   boolean isEnabled();

}

4、 PasswordEncoder接口

​ 这是一个密码加密和匹配的一个接口,这就接口有很多实现类,对应的就是各种加密算法,但是官方推荐使用BCryptPasswordEncoder(), 一般会把这个实现类注入到spring容器中。

@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

}
public interface PasswordEncoder {
    // 将一个字符串进行加密
    String encode(CharSequence rawPassword);
	// 第一个参数是原始的密码,第二个参数是加密后的算法,如果匹配就返回true,否则返回false
    boolean matches(CharSequence rawPassword, String encodedPassword);

    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

5、自定义登录逻辑

// 需要注入到spring容器中
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    // loadUserByUsername()  username参数是前端传来的登录账户参数,前端的参数名必须为username,不能改变
    // UserDetails 返回参数,也是一个接口,但是返回这个接口的实现类User
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1、 通过username从数据库中查询数据
        Admin admin = new Admin(1,"admin","123456"); // 模拟查询的数据
        if (admin==null){
            // 如果不存在就抛出异常
            throw new UsernameNotFoundException("用户名不存在");
        }
        // 返回一个user对象,第一个参数是参数username,
        // 第二个参数是数据库中查询的密码,这个参数必须通过passwordEncoder加密,不然也不会通过,数据库保存的密码一般都是加密了的,不会是明文密码
        // 第三个参数权限列表
        return new User(username,passwordEncoder.encode(admin.getAdminPassword()),
                        AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

6、自定义登录页面

​ 如果没有设置自定义的登录页面,就会使用springsecurity默认的登录页面。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    // 权限分配
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 拦截所有的请求
        http.authorizeRequests()
            // 为某些请求设置为所有人都可以访问
            .antMatchers("/","/index.html","/login.html","/rest.html","/assets/**","/fail.html").permitAll()
            // 任何请求都需要认证
            .anyRequest().authenticated();
        // 登录表单
        http.formLogin()
            // 自定义登录页面
            .loginPage("/login.html")
            // 设置前端的传入登录名的参数名,默认是username,可以修改为其他但是和前端对应
            .usernameParameter("user").passwordParameter("password")
            // 设置登录请求,如果是/login就认为是登录请求
            .loginProcessingUrl("/myLogin")
            // 登录成功的跳转页面
            /*
                    设置登录成功的页面:
                方式一: defaultSuccessUrl("url",true),如果不设置第二个参数为true的话,如果访问的访问的是一个不存在的页面
                        登录成功就会跳到这个不存在的url。
                方式二: successForwardUrl("url"),需要一个post请求,在MvcConfig设置的请求都是get请求,
                        需要写一个controller重定向到对应的页面。
                 */
            .successForwardUrl("/toMain")
            // 登录失败的跳转页面,注意登录失败的请求也会被认证,所以需要将失败的请求的认证放行
            // 还有一个failureUrl()与failureForwardUrl()的区别在于,前者需要一个get请求,而后者需要一个post请求
            // .failureUrl("/fail.html")
            .failureForwardUrl("/failLogin");
        // 关闭csrf防御
        http.csrf().disable();
    }
}

7、自定义登录成功和失败处理器

// 登录成功的处理器
.successHandler(new AuthenticationSuccessHandler() {
    // request、response
    // Authentication用户认证,可获得登录账号、密码和权限集合
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 重定向到指定页面
        response.sendRedirect(request.getContextPath()+"/main.html");
    }
})
// 登录失败的处理器
.failureHandler(new AuthenticationFailureHandler() {
    // request、response
    // exception异常处理
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        logger.info(exception);
        request.setAttribute("msg","登录失败!!");
        response.sendRedirect(request.getContextPath()+"/fail.html");
    }
});

处理器只能通过response请求从定向到某个页面,不能使用请求转发,只支持get请求的方法。

8、授权配置

​ 编写一个配置类SecurityConfig,这个继承 WebSecurityConfigurerAdapter类,然后重载这个类的方法。

@EnableWebSecurity  // 这个注解开启了Security
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
}
// 授权配置,链式编程
@Override
protected void configure(HttpSecurity http) throws Exception {
    // 拦截所有的请求
    http.authorizeRequests()
        // 为某些请求设置为所有人都可以访问
        .antMatchers("/","/index.html","/login.html","/rest.html","/assets/**","/fail.html").permitAll()
        // regexMatchers()通过正则表达式来匹配请求,
        // 第一个参数是请求方式,是一个enum类型,可以省略,省略后就是所有访问方法都可以访问
        // 不省略就是指定的访问方法才能访问
        .regexMatchers(HttpMethod.GET,".+[.]jpg").permitAll()
        // 表示这个请求时拥有root这个权限的才能访问,权限名严格区分大小写
        .antMatchers("/manager/**").hasAuthority("root")
        // 表示这个请求需要admin或者root的权限才能进入,参数是可变长参数
        .antMatchers("/user/**").hasAnyAuthority("admin","root")
        // 任何请求都需要认证
        .anyRequest().authenticated();
    // 没有权限,自动默认的登录请求
    http.formLogin();
}

// 6种内置的授权访问方法
/*
        // 所有人都可以访问
        static final String permitAll = "permitAll";
        // 所有人都不允许访问
        private static final String denyAll = "denyAll";
        // 匿名访问,和permitAll()类似
        private static final String anonymous = "anonymous";
        // 所有请求需要认证后才能访问
        private static final String authenticated = "authenticated";
        // 全认证访问,只能通过输入账号和密码登录后才能访问,不能通过记住我来访问
        private static final String fullyAuthenticated = "fullyAuthenticated";
        // 通过记住我可以进行访问
        private static final String rememberMe = "rememberMe";
 */

9、角色认证

​ 一个系统中拥有多种角色,需要根据不同的角色让其页面的展示效果不同,首先对登录的用户进行配置角色,然后判断角色能否访某个请求。

​ 在UserDetailsServiceImpl中授予用户角色。

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    // 1、 通过username从数据库中查询数据
    User user = new User(null, username, null);
    List<User> users = userMapper.queryUserByPojo(user);
    if (users.size()==0){
        // 如果不存在就抛出异常
        throw new UsernameNotFoundException("用户名不存在");
    }
    // 返回一个user对象,第一个参数是参数username,
    // 第二个参数是数据库中查询的密码,这个参数必须通过passwordEncoder加密,不然也不会通过,数据库保存的密码一般都是加密了的,不会是明文密码
    // 第三个参数权限列表,角色需要使用ROLE_开头表示这是一个角色,每个值之间使用逗号隔开
    return new org.springframework.security.core.userdetails.User(username,users.get(0).getPassword(),
                                                                  AuthorityUtils.commaSeparatedStringToAuthorityList("root,ROLE_用户"));
}

​ 请求角色的判断:

// 拦截所有的请求
http.authorizeRequests()
    // 为某些请求设置为所有人都可以访问
    .antMatchers("/","/index.html","/login.html","/rest.html","/assets/**","/fail.html").permitAll()
    // regexMatchers()通过正则表达式来匹配请求,
    // 第一个参数是请求方式,是一个enum类型,可以省略,省略后就是所有访问方法都可以访问
    // 不省略就是指定的访问方法才能访问
    .regexMatchers(HttpMethod.GET,".+[.]jpg").permitAll()
    //                // 表示这个请求时拥有root这个权限的才能访问,权限名严格区分大小写
    //                .antMatchers("/manager/**").hasAuthority("root")
    //                // 表示这个请求需要admin和root的权限才能进入,参数是可变长参数
    //                .antMatchers("/user/**").hasAnyAuthority("admin","root")
    // 表示这个请求需要拥有这个管理员角色才能访问,这是的角色不能加ROLE_为开头
    .antMatchers("/manager/**").hasRole("管理员")
    // 表示这个请求需要拥有这个管理员或者用户角色才能访问
    .antMatchers("/user/**").hasAnyRole("管理员","用户")
    // 任何请求都需要认证
    .anyRequest().authenticated();

10、记住我

springsecurity提供了记住我功能,通过简单的配置就能够实现。如果用户在登录的时候选择了记住我功能后,用户再次访问网站时springsecurity就帮我们从数据库中获取数据自动登录,所以需要使用到数据库的连接。

SecurityConfig配置类中,进行以下配置:

// 记住我的PersistentTokenRepository注入
@Bean
public PersistentTokenRepository getPersistentTokenRepository(){
    // 创建一个jdbcToken对象
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    // 设置数据源
    jdbcTokenRepository.setDataSource(druidDataSource);
    // 在第一次启动时创建一个表,用户存放数据,第二次启动时需要删除
    //        jdbcTokenRepository.setCreateTableOnStartup(true);
    return jdbcTokenRepository;
}

protected void configure(HttpSecurity http)这个方法中开启记住我功能:

// 记住我功能
http.rememberMe()
    // 设置前端的参数名,默认是remember-me
    .rememberMeParameter("remember")
    // 设置记住我功能的期限,默认是14天,单位是秒
    .tokenValiditySeconds(60*60*60)
    // 配置自定义登录逻辑
    .userDetailsService(userDetailsService)
    // 配置token
    .tokenRepository(this.getPersistentTokenRepository());

当每登录一次在数据库中新创建的表就会多一条记录:

SpringSecurity的基础操作,登录认证,授权认证等_第3张图片

11、SpringSecurity整合thymeleaf

导入依赖:

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
    <groupId>org.thymeleaf.extrasgroupId>
    <artifactId>thymeleaf-extras-springsecurity5artifactId>
dependency>

导入thymeleaf命名空间:

xmlns="http://www.w3.org/1999/xhtml"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"

11.1、获取登录信息

<div>
  登录账号:<span sec:authentication="name">span><br>
  登录账号:<span sec:authentication="principal.username">span><br>
  凭证:<span sec:authentication="credentials">span><br>
  权限和角色:<span sec:authentication="authorities">span><br>
  客户端地址:<span sec:authentication="details.remoteAddress">span><br>
  sessionId:<span sec:authentication="details.sessionId">span><br>
div>

11.2、权限判断


<div>
  是否登录:<span sec:authorize="isAuthenticated()">span>
  
  
  <button sec:authorize="hasAuthority('root')">删除button>
  <button sec:authorize="hasAnyAuthority('admin','root')">查看button>
  
  <button sec:authorize="hasRole('超级管理员')">我是超级管理员button>
  <button sec:authorize="hasAnyRole('超级管理员','管理员')">我是超级管理员和管理员button>
div>

11.3、注销配置

// 退出功能
http.logout()
    // 设置退出的请求url,默认是/logout
    .logoutUrl("/user/logout")
    // 设置退出成功后的跳转页面
    .logoutSuccessUrl("/login.html");

The default is that accessing the URL “/logout” will log the user out by invalidating the HTTP Session, cleaning up any rememberMe() authentication that was configured, clearing the SecurityContextHolder, and then redirect to “/login?success”。可以请求/logout,然后就会清除session,然后重定向到/login?success

<li> <a th:href="@{/user/logout}"> <i class="fa fa-lock">i> 退出系统 a> li>

12、csrf

CSRF(Cross-site request forgery),也被称为:one click attack/session riding,中文名称:跨站请求伪造,缩写为:CSRF/XSRF。

一般来说,攻击者通过伪造用户的浏览器的请求,向访问一个用户自己曾经认证访问过的网站发送出去,使目标网站接收并误以为是用户的真实操作而去执行命令。常用于盗取账号、转账、发送虚假消息等。攻击者利用网站对请求的验证漏洞而实现这样的攻击行为,网站能够确认请求来源于用户的浏览器,却不能验证请求是否源于用户的真实意愿下的操作行为。在springsecurity4以后就有了csrf防御,默认是开启的,但是在学习阶段都是关闭了csrf功能。

// 关闭csrf防御
http.csrf().disable();

springsecurity4以后为了防止crsf攻击,保证不是第三方网站访问,要求在访问时需要携带参数名为_csrf值为token的内容,token是服务器生成的。如果值和服务器的值得token匹配就允许访问。例如已登录为例,可以使用隐藏域来传入一个token值:


<input type="hidden" th:value="${_csrf}" name="_csrf" th:if="${_csrf}">

你可能感兴趣的:(微服务,java,spring,boot,spring)