SpringSecurity安全框架
底层有以下三个比较重要的过滤器:
FilterSecurityInterceptor
:是一个方法级的权限过滤器,基本位于过滤器的最底部。
ExceptionTranslationFilter
:是个异常过滤器,用来处理在认证授权过程中抛出的异常。
UsernamePasswordAuthenticationFilter
:对/login的POST请求做拦截,校验表单中用户名和密码。
使用
SpringSecurity
需要先配置过滤器
SpringBoot
帮助我们做了这一步。(ps:想了解可以自行了解)
开发过程中2个重要的接口
UserDetailService
接口:
实际开发中,我们的账号密码都是从数据库中查出来的,所以我们就需要通过自定义逻辑控制认证逻辑。这就需要我们去实现UserDetailService
接口。
过程:
-
创建一个类继承
UsernamePasswordAuthenticationFilter
==>重写attempAuthentication
方法,校验成功则调用successfulAuthentication
,失败则调用unsuccessfulAuthentication
-
创建类实现
UserDetailService
接口,编写查询数据库的过程,返回User对象,这个对象就是安全框架提供的对象
PasswordEncoder
接口:
数据加密接口,用于User对象里面密码加密
设置登录的用户名和密码
第一种方式:通过配置文件
第二种方式:通过配置类
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //加密 BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String encode = passwordEncoder.encode("123"); auth.inMemoryAuthentication().withUser("lucy").password(encode).roles("admin"); } //必需 @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
第三种方式:自定义编写实现类
@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(); } }
2.
@Service("userDetailsService") public class MyUserDetailService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role"); return new User("mery",new BCryptPasswordEncoder().encode("123"),auths); } }
整合Mybatis-Plus完成数据库操作
@Repository public interface UsersMapper extends BaseMapper<Users> { } @Service("userDetailsService") public class MyUserDetailService implements UserDetailsService { @Autowired private UsersMapper usersMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //调用mapper里面的方法去查数据库 QueryWrapper<Users> usersQueryWrapper = new QueryWrapper<>(); usersQueryWrapper.eq("username",username); Users user = usersMapper.selectOne(usersQueryWrapper); //判断 没有则认证为空 if (user == null) { throw new UsernameNotFoundException("用户名不存在!"); } List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role"); return new User(user.getUsername(), new BCryptPasswordEncoder().encode("123"),auths); } }
启动类:
@SpringBootApplication @MapperScan("com.mu.SpringSecurity.mapper") public class SpringSecurityApplication { public static void main(String[] args) { SpringApplication.run(SpringSecurityApplication.class, args); } }
自定义设置登录页面
1.在配置类种实现相关的配置
@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(); } @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() //自定义编写的页面 .loginPage("login.html") //登录页面设置 .loginProcessingUrl("/user/login") //登录访问路径 .defaultSuccessUrl("/test/index") //登录访问路径 .permitAll() .and().authorizeRequests() .antMatchers("/","/test/hello","user/login") //设置哪些路径可以直接访问,不需要认证 .permitAll() .anyRequest().authenticated() .and().csrf().disable(); //关闭csrf防护 } } @RestController @RequestMapping("/test") public class Test { @GetMapping("/hello") public String hello(){ return "hello"; } @GetMapping("/index") public String index(){ return "hello,index"; } }
2.创建相关的页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/user/login" method="post"> 用户名:<input type="text" name="username"> <br/> 密码:<input type="password" name="password"> <br/> <input type="submit" value="login"> </form> </body> </html>
如果当前的主体具有指定的权限,则返回true,否则返回false
1.在配置类设置当前访问地址有哪些权限
2.在UserDetailsService中,把返回User对象设置权限
(ps
:如没有权限,则会报403错误,)
该方法适用于当前主体具有指定权限.当主题有多个权限的时候,就不行了,就需要我们使用hasAnyAuthority
方法.
针对多个权限或多个角色
当前的主体中只要有admins,manager其中一个权限就可以访问
如果用户具备给定角色就允许访问,否则出现403.
如果当前主题具有指定的角色,则返回true.
底层源码:
这里的hasRole("sale")的返回值就是ROLE_sale.
然后再在MyUserDetailsService中,添加权限
类似hasAnyAuthority
方法,是针对用户拥有多个角色.
给用户添加角色:
修改配置文件:
unauth.html <body> <h1>对不起,您没有访问权限!</h1> </body>
在配置文件中直接配置:
http.exceptionHandling().accessDeniedPage("/unauth");
添加对应控制器:
@GetMapping("/unauth") public String accessDenyPage(){ return "unauth"; }
@Secured:
判断是否有这个角色,有这个角色可以访问方法,另外需要注意的是这里匹配的字符串需要添加前缀"ROLE_"。
@SpringBootApplication @MapperScan("com.mu.SpringSecurity.mapper") @EnableGlobalMethodSecurity(securedEnabled = true) public class SpringSecurityApplication { public static void main(String[] args) { SpringApplication.run(SpringSecurityApplication.class, args); } }
2、在Controller的方法上使用这个注解
@GetMapping("update") @Secured({"ROLE_sale","ROLE_manager"}) public String update(){ return "hello,update"; }
3、在MyUserDetailService种设置用户的角色
List<GrantedAuthority> auths =
AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
@PreAutorize
进入方法前进行方法的验证。
@PreAuthorize("hasAnyAuthority('admins')") @GetMapping("update") public String update(){ return "hello,update"; }
@PostAuthorize
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限.
1、先开启注解功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
2、在方法上面加上该注解
@PostAuthorize("hasAnyAuthority('admin')") @GetMapping("update") public String update(){ return "hello,update"; }
1、在配置类中添加退出配置
http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll
功能实现
1.创建数据库表
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;
2.配置类,注入数据源,配置操作数据库对象
//注入数据源 @Autowired private DataSource dataSource; // 配置对象 @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); // 自动创建表 // jdbcTokenRepository.setCreateTableOnStartup(true); return jdbcTokenRepository; }
3.配置类配置自动登录
.and().rememberMe().tokenRepository(persistentTokenRepository()) //设置记住我 .tokenValiditySeconds(60) //设置有效时长,单位秒 .userDetailsService(userDetailsService)
4.在登录页面添加复选框
<input type="checkbox" name="remember-me">自动登录
(ps:这里的name属性必须为"remember-me",不然系统无法找到)
CSRF理解
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跟
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的.
案例
在登录页面添加一个隐藏域:
<input type="hidden"th:if="${_csrf}!=null"th:value="${_csrf.token}"name="_csrf"/>
关闭安全配置的类中的csrf
http.csrf().disable();
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?