SpringSecurity快速上手

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: SpringSecurity 本质是一个过滤器链

依赖

       <dependency>

           <groupId>org.springframework.boot</groupId>

           <artifactId>spring-boot-starter-security</artifactId>

       </dependency>

基本原理

过滤器链

SpringSecurity 本质是一个过滤器链,有三个重要的过滤器:

  • FilterSecurityInterceptor:是一个方法级的权限过滤器, 基本位于过滤链的最底部。
  • ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常
  • UsernamePasswordAuthenticationFilter :对/login 的 POST 请求做拦截,校验表单中用户名,密码

两个接口

UserDetailsService 接口

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

如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。接口定义如下:

publicinterfaceUserDetailsService {

   UserDetailsloadUserByUsername(Stringvar1) throwsUsernameNotFoundException;

}

返回值 UserDetails ,这个类是系统默认的用户“主体”。接口实现类User

// 表示获取登录用户所有权限

Collection<?extendsGrantedAuthority>getAuthorities();

// 表示获取密码

StringgetPassword();

// 表示获取用户名

StringgetUsername();

// 表示判断账户是否过期

booleanisAccountNonExpired();

// 表示判断账户是否被锁定

booleanisAccountNonLocked();

// 表示凭证{密码}是否过期

booleanisCredentialsNonExpired();

// 表示当前用户是否可用

booleanisEnabled();

PasswordEncoder 接口

// 表示把参数按照特定的解析规则进行解析

Stringencode(CharSequencerawPassword);

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

参数表示存储的密码。

booleanmatches(CharSequencerawPassword, StringencodedPassword);

// 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回false。默认返回 false。

defaultbooleanupgradeEncoding(StringencodedPassword) {

   returnfalse;

}

接口实现类

BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器。

BCryptPasswordEncoder 是对 bcrypt 强散列方法的具体实现。是基于 Hash 算法实现的单向加密。可以通过 strength 控制加密强度,默认 10

使用示例:

@Test

publicvoidtest01(){

// 创建密码解析器

BCryptPasswordEncoderbCryptPasswordEncoder=newBCryptPasswordEncoder();

// 对密码进行加密

Stringatguigu=bCryptPasswordEncoder.encode("atguigu");

// 打印加密之后的数据

System.out.println("加密之后数据:\t"+atguigu);

//判断原字符加密后和加密之前是否匹配

booleanresult=bCryptPasswordEncoder.matches("atguigu", atguigu);

// 打印比较结果

System.out.println("比较结果:\t"+result);

}

Web 权限方案

  1. 认证
  2. 授权

查询数据库进行认证

  1. 编写userDetailsService接口的实现类并装载进容器,返回User对象,User对象有用户名密码和操作权限
  2. 创建配置类,使用返回的User对象进行认证

packagecn.upeveryday.service.impl;

@Service("userDetailsService")

publicclass_MyUserDetailsServiceImplimplementsUserDetailsService {

   @Autowired

   privateUserAuthMapperuserAuthMapper;

   List<GrantedAuthority>auths=AuthorityUtils.commaSeparatedStringToAuthorityList("role");

   @Override

   publicUserDetailsloadUserByUsername(Stringusername) throwsUsernameNotFoundException {

       //根据用户名查询数据库

       QueryWrapperwrapper=newQueryWrapper();

       wrapper.eq("username", username);

       UserAuthuserAuth=userAuthMapper.selectOne(wrapper);

       //判断

       if (userAuth==null){//数据库无此用户名,认证失败

           thrownewUsernameNotFoundException("用户名不存在!");

       }

       //权限参数不能为null

       returnnewUser(userAuth.getUsername(), newBCryptPasswordEncoder().encode(userAuth.getPassword()), auths);

   }

}

packagecn.upeveryday.config;

@Configuration

publicclassSecurityConfigextendsWebSecurityConfigurerAdapter {

   //注入我们写的实现类,内有用户名密码和操作权限

   @Autowired

   privateUserDetailsServiceuserDetailsService;

   @Override

   protectedvoidconfigure(AuthenticationManagerBuilderauth) throwsException {

       auth.userDetailsService(userDetailsService).passwordEncoder(password());

   }

   /**

    * 将密码加密器装载进容器

    * @return

    */

   @Bean

   PasswordEncoderpassword(){

       returnnewBCryptPasswordEncoder();

   }

}

其他配置

同样在配置类中

   @Override

   protectedvoidconfigure(HttpSecurityhttp) throwsException {

       // 配置认证

       http.formLogin()

               .loginPage("/index") // 配置哪个 url 为登录页面

               .loginProcessingUrl("/login") // 设置哪个是登录的 url。

               .successForwardUrl("/success") // 登录成功之后跳转到哪个 url

               .failureForwardUrl("/fail");// 登录失败之后跳转到哪个 url

       http.authorizeRequests()

               .antMatchers("/layui/**","/index") //表示配置请求路径

               .permitAll() // 指定 URL 无需保护。

               .anyRequest() // 其他请求

               .authenticated(); //需要认证

       // 关闭 csrf

       http.csrf().disable();

   }

基于角色或权限进行访问控制

hasAuthority 方法

如果当前的主体具有指定的权限,则返回 true,否则返回 false

  • 修改配置类

   @Override

   protectedvoidconfigure(HttpSecurityhttp) throwsException {

       // 配置认证

       http.formLogin()

               .loginPage("/index") // 配置哪个 url 为登录页面

               .loginProcessingUrl("/login") // 设置哪个是登录的 url。

               .successForwardUrl("/success") // 登录成功之后跳转到哪个 url

               .failureForwardUrl("/fail");// 登录失败之后跳转到哪个 url

       http.authorizeRequests()

               .antMatchers("/layui/**","/index").permitAll() //指定的 URL 无需保护

               .antMatchers("/test").hasAuthority("admin")//当前登录用户,只有具有admin权限才能访问这个路径

               .anyRequest() // 其他请求

               .authenticated(); //需要认证

       // 关闭 csrf

       http.csrf().disable();

   }

hasAnyAuthority 方法

满足任意一个权限即可访问

   @Override

   protectedvoidconfigure(HttpSecurityhttp) throwsException {

       // 配置认证

       http.formLogin()

               .loginPage("/index")

               .loginProcessingUrl("/login")

               .successForwardUrl("/success")

               .failureForwardUrl("/fail");

       http.authorizeRequests()

               .antMatchers("/layui/**","/index").permitAll()

//                .antMatchers("/test").hasAuthority("admin")

               .antMatchers("/test").hasAnyAuthority("admin,manager")//当前登录用户,具有其中一个权限即可访问这个路径

               .anyRequest()

               .authenticated();

       http.csrf().disable();

   }

hasRole 方法

如果当前主体具有指定的角色,则返回 true。

底层源码:

   privatestaticStringhasRole(Stringrole) {

       Assert.notNull(role, "role cannot be null");

       Assert.isTrue(!role.startsWith("ROLE_"), () -> {

           return"role should not start with 'ROLE_' since it is automatically inserted. Got '"+role+"'";

       });

       return"hasRole('ROLE_"+role+"')";

   }

注意:这里与添加权限不同,添加角色需要加上"ROLE_"前缀

admin是权限

ROLE_sale是角色

@Service("userDetailsService")

publicclass_MyUserDetailsServiceImplimplementsUserDetailsService {

   @Autowired

   privateUserAuthMapperuserAuthMapper;

   List<GrantedAuthority>auths=AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");

   ……

}

注意配置文件中不需要添加”ROLE_“,因为上述的底层代码会自动添加与之进行匹配

   @Override

   protectedvoidconfigure(HttpSecurityhttp) throwsException {

       // 配置认证

       http.formLogin()

               .loginPage("/index")

               .loginProcessingUrl("/login")

               .successForwardUrl("/success")

               .failureForwardUrl("/fail");

       http.authorizeRequests()

               .antMatchers("/layui/**","/index").permitAll()

//                .antMatchers("/test").hasAuthority("admin")

//                .antMatchers("/test").hasAnyAuthority("admin,manager")//当前登录用户,具有其中一个权限即可访问这个路径

               .antMatchers("/test").hasRole("sale")

               .anyRequest()

               .authenticated();

       http.csrf().disable();

   }

hasAnyRole方法

表示用户具备任何一个条件都可以访问。

注解使用

@Secured

判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。

使用注解先要开启注解功能!

在启动类或配置类添加注解都可

@SpringBootApplication

@MapperScan("cn.upeveryday.mapper")

@EnableGlobalMethodSecurity(securedEnabled=true)//开启security的注解功能

public class MyApplication {

   public static void main(String[] args) {

       SpringApplication.run(MyApplication.class, args);

   }

}

控制器方法上添加注解

   @RequestMapping("testSecured")

   @ResponseBody

   @Secured({"ROLE_normal","ROLE_admin"})

   public String helloUser() {

       return "hello,user";

   }

@PreAuthorize

先开启注解功能:

@EnableGlobalMethodSecurity(prePostEnabled = true)

@PreAuthorize:注解适合进入方法前的权限验证, @PreAuthorize 可以将登录用户的 roles/permissions 参数传到方法中。

四个方法都可使用

   @RequestMapping("/preAuthorize")

   @ResponseBody

   //@PreAuthorize("hasRole('ROLE_管理员')")

   @PreAuthorize("hasAnyAuthority('menu:system')")

   public String preAuthorize(){

       System.out.println("preAuthorize");

       return "preAuthorize";

   }

@PostAuthorize

先开启注解功能:

@EnableGlobalMethodSecurity(prePostEnabled = true)

@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限.

   @RequestMapping("/testPostAuthorize")

   @ResponseBody

   @PostAuthorize("hasAnyAuthority('menu:system')")

   public String preAuthorize(){

       System.out.println("test--PostAuthorize");

       return "PostAuthorize";

   }

@PostFilter

@PostFilter :权限验证之后对数据进行过滤 留下用户名是 admin1 的数据

表达式中的 filterObject 引用的是方法返回值 List 中的某一个元素

   @RequestMapping("getAll")

   @PreAuthorize("hasRole('ROLE_管理员')")

   @PostFilter("filterObject.username == 'admin1'")

   @ResponseBody

   public List<UserInfo> getAllUser(){

       ArrayList<UserInfo> list = new ArrayList<>();

       list.add(new UserInfo(1l,"admin1","6666"));

       list.add(new UserInfo(2l,"admin2","888"));

       return list;

   }

@PreFilter

@PreFilter: 进入控制器之前对数据进行过滤

   @RequestMapping("getTestPreFilter")

   @PreAuthorize("hasRole('ROLE_管理员')")

   @PreFilter(value = "filterObject.id%2==0")

   @ResponseBody

   public List<UserInfo> getTestPreFilter(@RequestBody List<UserInfo>

                                                  list){

       list.forEach(t-> {

           System.out.println(t.getId()+"\t"+t.getUsername());

       });

       return list;

   }

用户注销

在配置类中添加退出映射地址

http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();

自动登录

创建表

CREATE TABLE `persistent_logins` (

`username` varchar(64) NOT NULL,

`series` varchar(64) NOT NULL,

`token` varchar(64) NOT NULL,

`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE

CURRENT_TIMESTAMP,

PRIMARY KEY (`series`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

修改安全配置类

package cn.upeveryday.config;


@Configuration

public class SecurityConfig extends WebSecurityConfigurerAdapter {

   //注入我们写的实现类,内有用户名密码和操作权限

   @Autowired

   private UserDetailsService userDetailsService;


   @Override

   protected void configure(AuthenticationManagerBuilder auth) throws Exception {

       auth.userDetailsService(userDetailsService).passwordEncoder(password());

   }

   /**

    * 将密码加密器装载进容器

    * @return

    */

   @Bean

   PasswordEncoder password(){

       return new BCryptPasswordEncoder();

   }

//---------------------------------------------------------------------------

   //注入yaml配置的数据源(记住我的操作)

   @Autowired

   private DataSource dataSource;

   @Bean

   public PersistentTokenRepository persistentTokenRepository(){

       JdbcTokenRepositoryImpl jdbcTokenRepository = new

               JdbcTokenRepositoryImpl();

       // 赋值数据源

       jdbcTokenRepository.setDataSource(dataSource);

       // 自动创建表,第一次执行会创建,以后要执行就要删除掉!

       jdbcTokenRepository.setCreateTableOnStartup(true);

       return jdbcTokenRepository;

   }



   @Override

   protected void configure(HttpSecurity http) throws Exception {

       // 配置认证

       http.formLogin()

               .loginPage("/index")

               .loginProcessingUrl("/login")

               .successForwardUrl("/success")

               .failureForwardUrl("/fail");

       http.authorizeRequests()

               .antMatchers("/layui/**","/index").permitAll()

//                .antMatchers("/test").hasAuthority("admin")

//                .antMatchers("/test").hasAnyAuthority("admin,manager")//当前登录用户,具有其中一个权限即可访问这个路径

               .antMatchers("/test").hasRole("sale")

               .anyRequest()

               .authenticated()

            //设置记住我的操作

               .and().rememberMe().tokenRepository(persistentTokenRepository())//s

               .userDetailsService(userDetailsService)//指定用户

               .tokenValiditySeconds(60);//设置有效时长,单位s

   }

}

页面添加记住我复选框

此处:name 属性值必须位 remember-me.不能改为其他值

记住我:<input type="checkbox"name="remember-me"title="记住密码"/><br/>

登录结果返回json

由于前后端分离,我们登录成功或失败,只需返回json数据

步骤:

导入fastjson依赖

       <!--fastJson-->

       <dependency>

           <groupId>com.alibaba</groupId>

           <artifactId>fastjson</artifactId>

           <version>1.2.62</version>

       </dependency>

编写配置类

  • cn.upeveryday.config.SecurityConfig

认证过程需要successHandler和failureHandler,查看源码,分别是AuthenticationSuccessHandler接口和AuthenticationFailureHandler接口

package cn.upeveryday.config;


@Configuration

public class SecurityConfig extends WebSecurityConfigurerAdapter {

   //注入我们写的实现类,内有用户名密码和操作权限

   @Autowired

   private UserDetailsService userDetailsService;


   @Override

   protected void configure(AuthenticationManagerBuilder auth) throws Exception {

       auth.userDetailsService(userDetailsService).passwordEncoder(password());

   }

   /**

    * 将密码加密器装载进容器

    * @return

    */

   @Bean

   PasswordEncoder password(){

       return new BCryptPasswordEncoder();

   }


//--------------------------------------------

   //注入处理器

   @Autowired

   private AuthenticationSuccessHandlerImpl authenticationSuccessHandler;


   @Autowired

   private AuthenticationFailHandlerImpl authenticationFailHandler;


   @Override

   protected void configure(HttpSecurity http) throws Exception {

       // 对认证进行配置

       http.formLogin()

               .loginProcessingUrl("/login")

               .successHandler(authenticationSuccessHandler)

               .failureHandler(authenticationFailHandler)

       ;

       http.csrf().disable();

   }

}

编写接口实现类

  • cn.upeveryday.handler.AuthenticationSuccessHandlerImpl

package cn.upeveryday.handler;


/**

* 登录成功处理

*/

@Component

public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {

   @Override

   public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

       //返回json数据

       httpServletResponse.setContentType("application/json;charset=UTF-8");

       httpServletResponse.getWriter().write(JSON.toJSONString(Result.success().message(ResultInfo.LOGIN_SUCCESS)));

   }

}

  • cn.upeveryday.handler.AuthenticationFailHandlerImpl

package cn.upeveryday.handler;


/**

* 登录失败处理

*/

@Component

public class AuthenticationFailHandlerImpl implements AuthenticationFailureHandler {

   @Override

   public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {

       //返回json数据

       httpServletResponse.setContentType("application/json;charset=UTF-8");

       httpServletResponse.getWriter().write(JSON.toJSONString(Result.error().message(ResultInfo.LOGIN_FAILURE)));

   }

}

在module中通过mapState、mapGetters、mapActions和mapMutations等辅助函数来绑定要触发的函数



目录
相关文章
|
6月前
|
安全 算法 Java
SpringSecurity 快速入门
SpringSecurity 快速入门
92 3
|
Java Maven 开发者
写给 Rails 开发者的 Spring boot 教程
写给 Rails 开发者的 Spring boot 教程
|
安全 Java 数据库
SpringSecurity 入门
Spring Security是Spring采用 `AOP`思想,基于 `servlet过滤器`实现的安全框架。它提供了完善的**认证机制**和**方法级的授权功能**。是一款非常优秀的权限管理框架。
87 0
|
6月前
|
XML 安全 Java
轻松上手:Spring环境搭建的实用指南
轻松上手:Spring环境搭建的实用指南
61 1
轻松上手:Spring环境搭建的实用指南
|
5月前
|
Java 数据安全/隐私保护 Spring
SpringSecurity6从入门到实战之SpringSecurity快速入门
这篇文章是关于使用SpringSecurity 6进行快速入门的教程。首先介绍了所需的环境配置,包括SpringSecurity 6.0.8、SpringBoot 3.0.12和JDK 17。接着,通过步骤展示了如何创建一个新的SpringBoot工程,并添加Web支持。然后,运行工程并测试了Hello接口,确保其正常工作。之后,引入SpringSecurity依赖后,无需额外配置,系统即实现了基础的认证功能,自动重定向到登录页面。文章通过截图详细说明了这个过程,包括控制台日志、登录页面以及登录后的资源访问。
|
安全 Java 数据库
SpringSecurity入门
SpringSecurity入门
101 0
|
安全 前端开发 Java
一款基于SpringBoot+SpringSecurity的后台管理系统,强烈推荐
Base Admin一套简单通用的后台管理系统,主要功能有:权限管理、菜单管理、用户管理,系统设置、实时日志,API加密,以及登录用户修改密码、配置个性菜单等。
SpringSecurity框架
SpringSecurity框架
36 0
|
前端开发 安全 Java
【Swagger】Swagger-狂神笔记
【Swagger】Swagger-狂神笔记
171 2
下一篇
无影云桌面