三、SpringSecurity Web 权限方案
1、设置登录账号、密码
-
在 application.properties添加相应的配置
-
spring.security.user.name=test spring.security.user.password=test
-
-
编写配置类
-
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String password = bCryptPasswordEncoder.encode("test"); auth.inMemoryAuthentication().withUser("test").password(password).roles("admin"); } @Bean PasswordEncoder password(){ return new BCryptPasswordEncoder(); } }
-
-
自定义实现类
-
//自定义实现UserDetailsService接口 @Service public class MyUserDetailsService implements UserDetailsService { @Autowired private UsersMapper usersMapper; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { //给用户赋值权限、或者角色,也可以从数据库查询 List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("test"); //返回的是SpringSecurity自带的UserDeatils接口实现类 return new User("test","test",auths); } }
-
//编写配置类 @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() { @Override public String encode(CharSequence charSequence) { return charSequence.toString(); } @Override public boolean matches(CharSequence charSequence, String s) { return s.equals(charSequence.toString()); } }); } }
-
2、查询数据库完成验证
上面都是写死了登录帐号、密码,接下来演示通过数据库查询,整合了MybatisPlus自行学习
-
编写自定义类实现UserDetailsService接口
-
//编写自定义类实现UserDetailsService接口 @Service public class MyUserDetailsService implements UserDetailsService { @Autowired private UsersMapper usersMapper; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { QueryWrapper<Users> wrapper = new QueryWrapper<>(); wrapper.eq("username",s); Users users = usersMapper.selectOne(wrapper); //以上是MybatisPlus从数据库查询用户信息相关操作,自行学习 if( users == null){ throw new UsernameNotFoundException("用户名不存在!"); } //给用户赋值权限、或者角色,也可以从数据库查询 List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("test"); return new User(users.getUsername(), users.getPassword(), auths); } }
-
-
编写配置类
-
//编写配置类 @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() { @Override public String encode(CharSequence charSequence) { return charSequence.toString(); } @Override public boolean matches(CharSequence charSequence, String s) { return s.equals(charSequence.toString()); } }); } }
-
3、自定义登录页面
-
前端页面代码
-
<!DOCTYPE html> <head lang="en"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>login页面</title> </head> <body> <h1>表单提交</h1>ht <form action="/user/login" method="post"> <input type="text" name="username" /> <input type="text" name="password" /> <input type="submit" /> </form> </body> </html>
-
-
编写配置类
-
//编写配置类 @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login.html")//设置登录页面 .loginProcessingUrl("/user/login")//设置表单action提交路径 .defaultSuccessUrl("/test/index").permitAll();//登录成功后跳转的路径 http.authorizeRequests()//对于路径请求 .antMatchers("/", "/test/hello", "/user/login").permitAll()//这类路径直接放行 .anyRequest().authenticated();//其他的任何请求都需要认证 http.csrf().disable();//关系CSRF } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() { @Override public String encode(CharSequence charSequence) { return charSequence.toString(); } @Override public boolean matches(CharSequence charSequence, String s) { return s.equals(charSequence.toString()); } }); } }
-
4、自定义403访问页面
-
前端页面
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>没有权限访问页面</title> </head> <body> <h1>没有访问权限!</h1> </body> </html>
-
-
编写配置类
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { //自定义403访问页面 http.exceptionHandling().accessDeniedPage("/unauth.html"); http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/user/login") .defaultSuccessUrl("/test/index").permitAll(); http.authorizeRequests() .antMatchers("/", "/user/login").permitAll() .antMatchers("/test/hello").hasAnyRole("admin,test") .anyRequest().authenticated(); http.csrf().disable(); } }
5、自定义登录成功跳转页面
-
前端页面
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> 登录成功! <a href="/logout">退出</a> </body> </html>
-
-
编写配置类
-
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { //自定义403访问页面 http.exceptionHandling().accessDeniedPage("/unauth.html"); http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/user/login") .defaultSuccessUrl("/test/index").permitAll();//成功后跳转的路径 http.authorizeRequests() .antMatchers("/", "/user/login").permitAll() .antMatchers("/test/hello").hasAnyRole("admin,test") .anyRequest().authenticated(); http.csrf().disable(); } }
-
6、自定义登出功能
-
编写配置类
-
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.logout().logoutUrl("/logout").//自定义登出路径 logoutSuccessUrl("/test/hello").permitAll();//登出后跳转到的页面 //自定义403访问页面 http.exceptionHandling().accessDeniedPage("/unauth.html"); http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/user/login") .defaultSuccessUrl("/test/index").permitAll();//成功后跳转的路径 http.authorizeRequests() .antMatchers("/", "/user/login").permitAll() .antMatchers("/test/hello").hasAnyRole("admin,test") .anyRequest().authenticated(); http.csrf().disable(); } }
-
7、记住用户的功能实现
-
流程图:
-
实现原理:
-
实现流程
-
前端页面
-
<!DOCTYPE html> <head lang="en"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>login页面</title> </head> <body> <h1>表单提交</h1>ht <form action="/user/login" method="post"> <input type="text" name="username" /> <input type="text" name="password" /> <input type="checkbox"name="remember-me"title="记住密码"/><br/> <input type="submit" /> </form> </body> </html>
-
-
创建数据库表
-
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;
-
-
配置类中要注入数据源,配置操作数据库对象
-
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private DataSource dataSource; @Bean public PersistentTokenRepository persistentTokenRepository(){ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); return jdbcTokenRepository; } }
-
-
配置类中配置自动登录
-
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private DataSource dataSource; @Bean public PersistentTokenRepository persistentTokenRepository(){ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); return jdbcTokenRepository; } @Override protected void configure(HttpSecurity http) throws Exception { http.logout().logoutUrl("/logout"). logoutSuccessUrl("/test/hello").permitAll(); http.exceptionHandling().accessDeniedPage("/unauth.html"); http.formLogin() .loginPage("/login.html") .loginProcessingUrl("/user/login") .defaultSuccessUrl("/test/index").permitAll(); http.authorizeRequests() .antMatchers("/", "/user/login").permitAll() .antMatchers("/test/hello").hasAnyRole("admin,test") .anyRequest().authenticated(); http.rememberMe().tokenRepository(persistentTokenRepository())//开启记住用户的选项 .tokenValiditySeconds(60)//设置有效时长,单位秒 .userDetailsService(userDetailsService); http.csrf().disable(); } }
-
-
8、基于角色或权限进行访问控制
-
hasAuthority 方法
-
含义:如果当前的主体具有指定的权限,则返回 true,否则返回 false
-
.antMatchers("/test/hello").hasAuthority("admin")
-
-
-
hasAnyAuthority 方法
-
含义:如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true
-
.antMatchers("/test/hello").hasAnyAuthority("admin,test")
-
-
hasRole 方法
-
含义:如果用户具备给定角色就允许访问,否则出现 403。如果当前主体具有指定的角色,则返回 true。
-
.antMatchers("/test/hello").hasRole("test")
-
注意事项:
-
-
hasAnyRole
-
含义:表示用户具备任何一个角色都可以访问。
-
.antMatchers("/test/hello").hasAnyRole("admin,test")
-
9、认证授权注解使用
-
@Secured
-
需要在配置类上先开启注解功能**@EnableGlobalMethodSecurity(securedEnabled=true)**
-
含义:判断是否具有角色,另外需要注意的是**这里匹配的字符串需要添加前缀“ROLE_“**。
-
@RestController @RequestMapping("test") public class IndexController { @GetMapping("hello") @Secured({"ROLE_role"}) public String hello() { return "hello security"; } }
-
-
@PreAuthorize
-
需要在配置类上先开启注解功能**@EnableGlobalMethodSecurity(prePostEnabled = true)**
-
含义:适合进入方法前的权限验证。判断是否有权限进入方法
-
@RestController @RequestMapping("test") public class IndexController { @GetMapping("hello") @PreAuthorize("hasAnyAuthority('admin,test')") public String hello() { return "hello security"; } }
-
-
@PostAuthorize
-
需要在配置类上先开启注解功能**@EnableGlobalMethodSecurity(prePostEnabled = true)**
-
含义:在方法执行后再进行权限验证。是否有权限返回。
-
@RestController @RequestMapping("test") public class IndexController { @GetMapping("hello") @PostAuthorize("hasAnyAuthority('admin,test')") public String hello() { return "hello security"; } }
-
-
@PreFilter
-
含义:进入控制器之前对数据进行过滤。
-
@RestController @RequestMapping("test") public class IndexController { @GetMapping("hello") @PreFilter(value = "filterObject.id%2==0")//只允许id为偶数的进入方法 public String hello() { return "hello security"; } }
-
-
@PostFilter
-
含义:方法返回时对数据进行过滤。
-
@RestController @RequestMapping("test") public class IndexController { @GetMapping("hello") @PostFilter("filterObject.username == 'admin1'")//只留下用户名是 admin1 的数据 public String hello() { return "hello security"; } }
-
10、CSRF应用
-
CSRF 理解
- 跨站请求伪造也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF
- 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
- 跨站请求攻击,简单地说,是**攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)**。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。
- 利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的
-
Spring Security 实现 CSRF 的原理:
- 生成 csrfToken 保存到 HttpSession 或者 Cookie 中。
- 请求到来时,从请求中提取 csrfToken,和保存的 csrfToken 做比较,进而判断当前请求是否合法。主要通过 CsrfFilter 过滤器来完成。
-
实现过程:
需要整合thymeleaf,自行学习
-
前端页面里面添加 <input type=“hidden” name="${_csrf.parameterName}" value=“${_csrf.token}” />
-
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head lang="en"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>xx</title> </head> <body> <h1>表单提交</h1> <!-- 表单提交用户信息,注意字段的设置,直接是*{} --> <form action="/user/login" method="post"> //需要添加下面这行存储csrftoken <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> <input type="text" name="username" /> <input type="text" name="password" /> <input type="checkbox"name="remember-me"title="记住密码"/><br/> <input type="submit" /> </form> </body> </html>
-
-
配置类中去掉 http.csrf().disable();
-