SpringSecurity的使用

介绍

Spring Security 是一个高度自定义的安全框架。利用 Spring IoC/DI 和 AOP 功能,为系统提供了声明式安全访问控制功能,减少了为系 统安全而编写大量重复代码的工作。主要实现两个功能:

  1. 用户登录的控制
  2. 登录后权限的控制

使用

引入依赖

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
 </dependency>

引入了依赖,其实就已经完成了配置,当我请求我们所要进入的页面时,都会先进行登录的验证,才会跳转到我们访问的页面。但是在实际的使用中,我们一般会跳转到我们请求的页面做安全的控制以及权限的控制,因此我们需要自定义对应的类。

UserDetailsService接口

当什么也没有配置的时候,账号和密码是由 Spring Security 定义 生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所 以我们要通过自定义逻辑控制认证逻辑。

三个参数具体解释:

username:用户名,这里默认提交表单中的 name 必须叫username

password:密码

authorities:用户具有的权限。此处不允许为 null

此处的用户名应该是客户端传递过来的用户名。而密码应该是从 数据库中查询出来的密码。Spring Security 会根据 User 中的 password 和客户端传递过来的 password 进行比较。如果相同则表示认证通过, 如果不相同表示认证失败。authorities 里面包含的所有内容为此用户具有的权限,如有里面没有包含某个权限,而在做 某个事情时必须包含某个权限则会出现 403。通常都是通过 AuthorityUtils.commaSeparatedStringToAuthorityList(“”) 来 创 建 authorities 集合对象的。参数时一个字符串,多个权限使用逗号分隔。

如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可,就可以进行对应的登录配置。

BCryptPasswordEncoder

BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器。BCryptPasswordEncoder 是对 bcrypt 强散列方法的具体实现。是 基于 Hash 算法实现的单向加密。

encode():把参数按照特定的解析规则进行解析。

matches()验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;如果不匹配,则返回 false。 第一个参数表示需要被解析的密码。第二个参数表示存储的密码。

//加密的使用
public void test(){
        BCryptPasswordEncoder pe = new BCryptPasswordEncoder();
        String encode = pe.encode("123");//加密
        System.out.println(encode);

        boolean matches = pe.matches("1234", encode);//原数据和加密匹配
        System.out.println(matches);//false
    }

自定义登录逻辑

当进行自定义登录逻辑时需要用到之前讲解的 UserDetailsService 和 PasswordEncoder。但是 Spring Security 要求:当进行自定义登录逻辑时容器内必须有 PasswordEncoder 实例。因此通过创建配置类注入该对象

  • PasswordEncoder配置类
@Configuration
public class SecurityConfig{
 @Bean
    public PasswordEncoder getPe() {
        return new BCryptPasswordEncoder();
    }
}
  • 编写登录用户逻辑,需要实现UserDetailsService
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder encoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1.查询数据库,查询用户名是否存在,如果不存在抛出UsernameNotFoundException
        if (!username.equals("admin")){
            throw new  UsernameNotFoundException("用户名不存在");
        }
        //2.把查询出来的密码进行解析,或直接把构造方法放到构造方法中
        //password 就是数据库中查询出来的密码,查询内容不是 123
        String password = encoder.encode("123");//加密后的数据,这里是模拟数据库中的密码
      //三个参数:用户名,数据库加密后的密码,实现的权限,角色,可访问的页面
        return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_adminN,/main.html,/main1.html"));
    }
}

自定义页面逻辑

简单说就是,你要设置那个页面做主页,那些页面不用验证,那些页面需要认证,那个角色或者权限能访问那个页面

该。配置类需要继承 WebSecurityConfigurerAdapte,并重写 configure 方法。

  • 常用的配置中调用的方法

    successForwardUrl()登录成功后跳转地址,使用 successForwardUrl()时表示成功后转发请求到地址。内部是通过 successHandler()方法进行控制成功后交给哪个类进行处理。ForwardAuthenticationSuccessHandler 内部就是最简单的请求转发。由于是请求转发,当遇到需要跳转到站外或在前后端分离的项目中就无法使用了。

    当需要控制登录成功后去做一些事情时,可以进行自定义认证成功控制器。这里以成功控制器为例,自定义认证失败控制器只需要继承AuthenticationFailureHandler即可

    //自定义登录成功处理器
    public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
        private String url;
    
        public MyAuthenticationSuccessHandler(String url) {
            this.url = url;
        }
    
        @Override
        public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
            System.out.println(httpServletRequest.getRemoteAddr());
            User user = (User) authentication.getPrincipal();
            System.out.println(user.getPassword());//默认 null
            System.out.println(user.getAuthorities());//权限
            httpServletResponse.sendRedirect(url);
        }
    }
    

    loginPage() 登录页面

    loginProcessingUrl 登录页面表单提交地址,此地址可以不真实存在。

    antMatchers():匹配内容 permitAll():允许

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private MyAccessDeniedHandler myAccessDeniedHandler;
    
        @Autowired
        private UserDetailServiceImpl userDetailService;
    
        @Autowired
        private DataSource dataSource;
    
        @Autowired
        private PersistentTokenRepository repository;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            System.out.println("执行UserDetailServiceImpl");
            //表单认证(登录)
            http.formLogin()
                    .loginProcessingUrl("/login")//当发现/login时认为是登录,需要执行UserDetailServiceImpl
                     .successForwardUrl("/toMain")//此处是一个 post 请求不能写.html 页面,静态资源只能走 get 请求
                    //自定义跳转
                   // .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
                    .failureHandler(new MyAuthenticationFailHandler("/fail.html"))
                    //.failureForwardUrl("/fail")
                    .loginPage("/showLogin");//登录页面
    
    
            //url 拦截(授权)
            http.authorizeRequests()
                    .antMatchers("/showLogin", "/fail.html").permitAll()//login.html 不需要被认证
                    // .mvcMatchers("/login.html").servletPath("/hello").permitAll()
                   // .antMatchers("/main1.html").hasIpAddress("0:0:0:0:0:0:0:1")//具有其中一个就能访问
                    //.anyRequest().access("@myServiceImpl.hasPermission(request,authentication)");//所有的请求都必须被认证,也就是必须登录后才能访问
                    .anyRequest().authenticated();
    
            //关闭 csrf
           // http.csrf().disable();
    
            //异常
            http.exceptionHandling()
                    .accessDeniedHandler(myAccessDeniedHandler);
    
            http.rememberMe()
                    //.tokenValiditySeconds()设置有效时间默认 2 周
                    .userDetailsService(userDetailService)//用户登录逻辑写在那个对象中
                    .tokenRepository(repository);
    
            //退出
            http.logout()
                    .logoutSuccessUrl("/showLogin");
        }
    
    
        @Bean
        public PersistentTokenRepository getPer(){
            JdbcTokenRepositoryImpl jt = new JdbcTokenRepositoryImpl();
            jt.setDataSource(dataSource);//需要一个数据源
            //jt.setCreateTableOnStartup(true);//第一次需要使用
            return jt;
        }
    
悲观者正确,乐观者成功
原文地址:https://www.cnblogs.com/freebule/p/14462626.html