文章目录
SpringSecurity入门
本文属于SpringSecurity入门篇,后续学习过程中会持续更新
基于spring的安全框架
前言
什么是安全框架?
解决系统安全问题的框架,如果没有安全框架,我们就需要手动处理每个资源的访问控制,显得非常麻烦。使用安全框架后就可以使用配置的方式对资源进行访问控制。
常见的安全框架
- Apache Shiro:一个功能强大且易于使用的安全框架,提供了认证,授权,加密,会话管理。
- Spring Security:Spring家族中的一员,是一个能够为应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组Spring上下文配置的Bean,利用SpringIoc,DI和AOP功能,为应用系统提供声明式的安全访问控制功能,减少了企业系统安全控制编写大量重复代码工作。
1 SpringSecurity概述
SpringSecurity是一个高度自定制的安全框架,他利用SpringIoc,DI,AOP功能,为应用系统提供声明式的安全访问控制功能,减少企业系统安全控制编写大量重复代码工作。
SpringSecurity两大核心功能
- 认证:是建立一个主体的过程("主体"一般指用户,设备或可以在应用程序中执行动作的其他系统),简单说是指使用者通过账户名和密码登陆的整个过程称为认证。
- 授权:是指一个主体是否允许在应用程序中执行某个动作的过程,简单说就是给某个用户指定某个功能的访问权限。
- SpringSecurity通过过滤器实现请求拦截,实现认证,授权等操作。
2 SpringSecurity的基本使用
- 引入Springboot和SpringSecurity相关依赖
<parent><groupId>org.springframework.boot</groupId><version>2.7.9</version><artifactId>spring-boot-starter-parent</artifactId></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
- 创建springboot启动类
@SpringBootApplicationpublicclassApplication{publicstaticvoidmain(String[] args){SpringApplication.run(Application.class);}}
- 创建Controller
@RestController@RequestMapping("/test")publicclassTestController{@RequestMapping("/test01")publicStringtest01(){return"test01";}@RequestMapping("/test02")publicStringtest02(){return"test02";}}
- 启动测试跳入如下页面> 当在项目中引入SpringSecurity依赖后,整个项目就会被SpringSecurity管理起来> > 未认证用户无权进入系统,会自动跳转到SpringSecurity提供的登陆页面
- 如图我们需要登陆才能访问密码在每次服务器启动时,控制台会自动生成一串字符串,如图所示默认用户名:user
- 输入用户名和密码,进入test01页面
- 自定义SpringSecurity登陆页面> 如果要使用自定义页面,需要SpringSecurity配置类中指定- 继承
WebSecurityConfigurerAdapter
-public class SecurityConfig extends
WebSecurityConfigurerAdapter> 继承的方式可能会存在安全隐患,此方式已过时- 组装式定义SpringSecurity配置类/** * SpringSecurity配置类 */@ConfigurationpublicclassSecurityConfig{}
- SecurityFilterChain方法> 用于拦截请求,并对请求进行处理
/** * 用于拦截请求,并对请求进行处理 * @param httpSecurity * @return SecurityFilterChain:Security过滤器链 */@Bean@AutowiredpublicSecurityFilterChainsecurityFilterChain(HttpSecurity httpSecurity)throwsException{ httpSecurity .csrf().disable()//禁用跨域请求伪造的攻击//请求配置.authorizeRequests()//获得所有认证请求//防止发生重定向次数过多错误,需要将login.html放行.antMatchers("/login.html","/fail.html")//匹配指定的路径.permitAll()//无需认证即可访问.anyRequest()//获得任意请求.authenticated()//必须认证才允许访问.and()//配置form表单.formLogin()//配置表单.loginProcessingUrl("/login")//配置登陆处理器的url地址,该地址所对应处理类由SpringSecurity提供.loginPage("/login.html")//配置登陆页.failureUrl("/fail.html")//登陆失败处理页;return httpSecurity.build();//获得securityFilterChain的实现类对象并返回}
- 上述拦截到页面直接无需认证让其放行,还有一种方式> 若拦截页面很多,则使用下列方式
/** * 处理需要忽略的请求 * @return */@BeanpublicWebSecurityCustomizerwebSecurityCustomizer(){returnnewWebSecurityCustomizer(){@Overridepublicvoidcustomize(WebSecurity web){//配置不拦截的路径(要忽略的路径) web.ignoring().antMatchers("/login.html","/fail.html");}};}
3 SpringSecurity基于内置账户的实现
- 设置账户时,每个账户都必须有一个角色
- 密码必须加密,通过
BCryptPasswordEncoder
进行加密
SpringSecurity把设置的内存密码交给加密器进行加密,获得一个"盐",通过"盐"和输入的密码进行加密获得一个加密后的字符串,和设置的内存密码进行匹配,若一样登陆成功
用于处理认证和授权逻辑
- 该方法内,可以定义认证和授权相关操
- 此处功能: -
设置内存账户,根据内存账户自定义用户名和密码进行登陆
-`@param builder` 认证管理器的编译器对象,该对象由springSecurity自动注入
/**
*用于处理认证和授权逻辑
* 该方法内,可以定义认证和授权相关操作
* 此处功能:
* -设置内存账户,根据内存账户自定义用户名和密码进行登陆
* @param builder 认证管理器的编译器对象,该对象由springSecurity自动注入
*/@AutowiredpublicvoidregisterProvider(AuthenticationManagerBuilder builder)throwsException{//SpringSecurity要求密码必须加密,创建加密器对象PasswordEncoder passwordEncoder =newBCryptPasswordEncoder();
builder
.inMemoryAuthentication()//设置内存账户.passwordEncoder(passwordEncoder)//设置密码加密器对象.withUser("admin")//设置内置账户用户名.password(passwordEncoder.encode("123456"))//设置内置账户密码//SpringSecurity要求每个账户必须有一个角色,此处定义账户为设置角色.roles("USER")//设置角色;}
- 加入角色后,每个资源都必须设置一个角色> 在SecurityFilterChain方法设置
.antMatchers("/index.html").hasAnyRole("USER","ADMIN").antMatchers("/test/test01").hasRole("ADMIN").antMatchers("/test/test02").hasAnyRole("USER")
- **上面设置允许角色"USER","ADMIN"
访问资源index.html
****允许角色"USER"
访问资源test/test01
**允许角色"ADMIN"
访问资源test/test02
4 SpringSecurity基于数据库的实现
- 创建bean
@Data@AllArgsConstructor@NoArgsConstructorpublicclassUserInfoimplementsSerializable{privateInteger user_id;privateString user_name;privateString user_password;privateString user_email;privateDate user_birthday;privateString user_hobbys;privateInteger user_sex;privateString user_address;privateInteger user_status;}
- 声明
UserDetailsService
对象并注入进认证管理器@AutowiredprivateUserDetailsService userDetailsService;@AutowiredpublicvoidregisterProvider(AuthenticationManagerBuilder builder)throwsException{//SpringSecurity要求密码必须加密,创建加密器对象PasswordEncoder passwordEncoder =newBCryptPasswordEncoder();/** * 基于数据库 */ builder.userDetailsService(userDetailsService)//用于指定登陆的处理逻辑(认证逻辑)的对象.passwordEncoder(passwordEncoder)//设置密码加密器;}
- 创建
UserDetailsServiceImpl
类并实现UserDetailsService
接口> 该类需要实现接口中的>> loadUserByUsername>
> 方法,该方法返回值为>> UserDetails>
> > > 该方法作用> > > 1. 根据用户名获得用户信息> 2. 将用户信息认证需要的数据封装到UserDetails的实现类对象中(User)> 3. 将封装好的认证信息提交给SpringSecurity进行认证UserDetails
接口中方法:-Collection<? extends GrantedAuthority> getAuthorities();
获得权限集合-String getPassword();
获得账号-String getUsername();
获得密码-boolean isAccountNonExpired();
判断账户是否过期(有效)-boolean isAccountNonLocked();
判断账户是否被冻结-boolean isCredentialsNonExpired();
凭证是否过期-boolean isEnabled();
账户是否启用/** * 处理认证逻辑的类 * + 该类需要重写接口中的loadUserByUsername方法根据用户名获得用户对象 */@ServicepublicclassUserDetailsServiceImplimplementsUserDetailsService{/** * 1.根据用户名获得用户信息 * 2.将用户信息认证需要的数据封装到UserDetails的实现类对象中(User) * 3.将封装好的认证信息提交给SpringSecurity进行认证 * @param username * @return * @throws UsernameNotFoundException */@OverridepublicUserDetailsloadUserByUsername(String username)throwsUsernameNotFoundException{returnnull;}}
- 由于要根据用户名获得用户信息则创建
UserMapper
接口@RepositorypublicinterfaceUserMapper{/** * * 根据用户名获得用户对象 * @param username * @return */@Select("select * from tbl_user where user_name=#{username}")publicUserInfogetUserByUsername(String username);}
- 根据三步完善
UserDetailsServiceImpl
@ResourceprivateUserMapper userMapper;/** * 1.根据用户名获得用户信息 * 2.将用户信息认证需要的数据封装到UserDetails的实现类对象中(User) * 3.将封装好的认证信息提交给SpringSecurity进行认证 * @param username * @return * @throws UsernameNotFoundException */@OverridepublicUserDetailsloadUserByUsername(String username)throwsUsernameNotFoundException{//根据用户名获得用户信息UserInfo userInfo = userMapper.getUserByUsername(username);//检测用户名是否正确if(userInfo==null){ log.info("用户名不存在");returnnull;}//检测用户是否为有效用户if(userInfo.getUser_status()==-1){ log.info("用户被冻结");returnnull;}List<GrantedAuthority> authorities =newArrayList<>(); authorities.add(newSimpleGrantedAuthority("ROLE_USER"));//将UserInfo中认证需要数据封装到User对象中(UserDetails的实现类对象)User user =newUser(userInfo.getUser_name()//附加数据,userInfo.getUser_password()//密码,true//账户是否启用,true//账户是否过期,true//凭证是否过期,userInfo.getUser_status()==-1?false:true//账户是否被锁定,authorities);//账户拥有的权限return user;//将该返回值交给SpringSecurity进行认证}
ps:小知识点
- 前端的文本框和密码框name只能是username和password,要想使用其他name该如何解决
在配置from表单里设置
.formLogin()//配置表单.loginProcessingUrl("/login")//配置登陆处理器的url地址,该地址所对应处理类由SpringSecurity提供.loginPage("/login.html")//配置登陆页.failureUrl("/fail.html")//登陆失败处理页.usernameParameter("myusername")//前端文本框的name.passwordParameter("mypassword")//前端密码框的name
- 学习来自于西安加中实训
版权归原作者 @活着笑 所有, 如有侵权,请联系我们删除。