作者:清茶淡粥酱 \
链接:https://juejin.cn/post/702673…
Spring Security 简介
Spring Security 是一种高度自定义的平安框架,利用(基于)SpringIOC/DI 和 AOP 性能,为零碎提供了申明式平安访问控制性能,缩小了为系统安全而编写大量反复代码的工作。
外围性能:认证和受权
Spring Security 认证流程
Spring Security 我的项目搭建
导入依赖
Spring Security 曾经被 Spring boot 进行集成,应用时间接引入启动器即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Spring Boot 根底就不介绍了,举荐下这个实战教程:
https://github.com/javastacks…
拜访页面
导入 spring-boot-starter-security 启动器后,Spring Security 曾经失效,默认拦挡全副申请,如果用户没有登录,跳转到内置登录页面。
在浏览器输出:http://localhost:8080/ 进入 Spring Security 内置登录页面
用户名:user
明码:我的项目启动,打印在控制台中
自定义用户名和明码
批改application.yml 文件
# 动态用户,个别只在外部网络认证中应用,如:外部服务器 1,拜访服务器 2
spring:
security:
user:
name: test # 通过配置文件,设置动态用户名
password: test # 配置文件,设置动态登录明码
UserDetailsService 详解
什么也没有配置的时候,账号和明码是由 Spring Security 定义生成的。而在理论我的项目中账号和明码都是从数据库中查问进去的。所以咱们要通过 自定义逻辑管制认证逻辑。如果须要自定义逻辑时,只须要实现 UserDetailsService 接口
@Component
public class UserSecurity implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {User user = userService.login(userName);
System.out.println(user);
if (null==user){throw new UsernameNotFoundException("用户名谬误");
}
org.springframework.security.core.userdetails.User result =
new org.springframework.security.core.userdetails.User(userName,user.getPassword(), AuthorityUtils.createAuthorityList());
return result;
}
}
举荐一个 Spring Boot 基础教程:
https://github.com/javastacks…
PasswordEncoder 明码解析器详解
PasswordEncoder
PasswordEncoder 是 SpringSecurity 的明码解析器,用户明码校验、加密。自定义登录逻辑时要求必须给容器注入 PaswordEncoder 的 bean 对象
SpringSecurity 定义了很多实现接口PasswordEncoder 满足咱们明码加密、明码校验 应用需要
自定义明码解析器
- 编写类,实现 PasswordEncoder 接口
/**
* 凭证匹配器,用于做认证流程的凭证校验应用的类型
* 其中有 2 个外围办法
* 1. encode - 把明文明码,加密成密文明码
* 2. matches - 校验明文和密文是否匹配
* */
public class MyMD5PasswordEncoder implements PasswordEncoder {
/**
* 加密
* @param charSequence 明文字符串
* @return
*/
@Override
public String encode(CharSequence charSequence) {
try {MessageDigest digest = MessageDigest.getInstance("MD5");
return toHexString(digest.digest(charSequence.toString().getBytes()));
} catch (NoSuchAlgorithmException e) {e.printStackTrace();
return "";
}
}
/**
* 明码校验
* @param charSequence 明文,页面收集明码
* @param s 密文,数据库中寄存明码
* @return
*/
@Override
public boolean matches(CharSequence charSequence, String s) {return s.equals(encode(charSequence));
}
/**
* @param tmp 转 16 进制字节数组
* @return 饭回 16 进制字符串
*/
private String toHexString(byte [] tmp){StringBuilder builder = new StringBuilder();
for (byte b :tmp){String s = Integer.toHexString(b & 0xFF);
if (s.length()==1){builder.append("0");
}
builder.append(s);
}
return builder.toString();}
}
2. 在配置类中指定自定义明码凭证匹配器
/**
* 加密
* @return 加密对象
* 如需应用自定义明码凭证匹配器 返回自定义加密对象
* 例如: return new MD5PasswordEncoder();
*/
@Bean
public PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(); //Spring Security 自带
}
登录配置
形式一 转发
http.formLogin()
.usernameParameter("name") // 设置申请参数中,用户名参数名称。默认 username
.passwordParameter("pswd") // 设置申请参数中,明码参数名称。默认 password
.loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么?默认 /login
.loginProcessingUrl("/login") // 用户登录逻辑申请地址是什么。默认是 /login
.failureForwardUrl("/failure"); // 登录失败后,申请转发的地位。Security 申请转发应用 Post 申请。默认转发到:loginPage?error
.successForwardUrl("/toMain"); // 用户登录胜利后,申请转发到的地位。Security 申请转发应用 POST 申请。
形式二:重定向
http.formLogin()
.usernameParameter("name") // 设置申请参数中,用户名参数名称。默认 username
.passwordParameter("pswd") // 设置申请参数中,明码参数名称。默认 password
.loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么?默认 /login
.loginProcessingUrl("/login") // 用户登录逻辑申请地址是什么。默认是 /login
.defaultSuccessUrl("/toMain",true); // 用户登录胜利后,响应重定向到的地位。GET 申请。必须配置相对地址。.failureUrl("/failure"); // 登录失败后,重定向的地位。
形式三:自定义登录处理器
自定义登录失败逻辑处理器
/* 自定义登录失败处理器 */
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private String url;
private boolean isRedirect;
public MyAuthenticationFailureHandler(String url, boolean isRedirect) {
this.url = url;
this.isRedirect = isRedirect;
}
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {if (isRedirect){httpServletResponse.sendRedirect(url);
}else {httpServletRequest.getRequestDispatcher(url).forward(httpServletRequest,httpServletResponse);
}
}
//get set 办法 省略
自定义登录胜利逻辑处理器
/**
* 自定义登录胜利后处理器
* 转发重定向,有代码逻辑实现
* */
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private String url;
private boolean isRedirect;
public MyAuthenticationSuccessHandler(String url, boolean isRedirect) {
this.url = url;
this.isRedirect = isRedirect;
}
/**
* @param request 申请对象 request.getRequestDispatcher.forward()
* @param response 响应对象 response.sendRedirect()
* @param authentication 用户认证胜利后的对象。其中报换用户名权限联合,内容是
* 自定义 UserDetailsService
* */
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {if (isRedirect){response.sendRedirect(url);
}else {request.getRequestDispatcher(url).forward(request,response);
}
}
//get set 办法 省略
http.formLogin()
.usernameParameter("name") // 设置申请参数中,用户名参数名称。默认 username
.passwordParameter("pswd") // 设置申请参数中,明码参数名称。默认 password
.loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么?默认 /login
.loginProcessingUrl("/login") // 用户登录逻辑申请地址是什么。默认是 /login
登录相干配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserSecurity userSecurity;
@Autowired
private PersistentTokenRepository persistentTokenRepository;
/**
* 加密
* @return 加密对象
* 如需应用自定义加密逻辑 返回自定义加密对象
* return new MD5PasswordEncoder(); return new SimplePasswordEncoder();
*/
@Bean
public PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(); //Spring Security 自带
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 配置登录申请相干内容。http.formLogin()
.loginPage("/toLogin") // 当用户未登录的时候,跳转的登录页面地址是什么?默认 /login
.usernameParameter("name") // 设置申请参数中,用户名参数名称。默认 username
.passwordParameter("pswd") // 设置申请参数中,明码参数名称。默认 password
.loginProcessingUrl("/login") // 设置登录 提交表单数据拜访申请地址
.defaultSuccessUrl("/toMain")
.failureUrl("/toLogin");
//.successForwardUrl("/toMain")
//.failureForwardUrl("/toLogin");
//.successHandler(new LoginSuccessHandler("/toMain", true)) // 自定义登录胜利处理器
//.failureHandler(new LoginErrorHandler("/toLogin", true));
http.authorizeRequests()
//.antMatchers("/toLogin").anonymous() // 只能匿名用户拜访
.antMatchers("/toLogin", "/register", "/login", "/favicon.ico").permitAll() // /toLogin 申请地址,能够轻易拜访。.antMatchers("/**/*.js").permitAll() // 授予所有目录下的所有.js 文件可拜访权限
.regexMatchers(".*[.]css").permitAll() // 授予所有目录下的所有.css 文件可拜访权限
.anyRequest().authenticated(); // 任意的申请,都必须认证后能力拜访。// 配置退出登录
http.logout()
.invalidateHttpSession(true) // 回收 HttpSession 对象。退出之前调用 HttpSession.invalidate() 默认 true
.clearAuthentication(true) // 退出之前,清空 Security 记录的用户登录标记。默认 true
// .addLogoutHandler() // 减少退出处理器。.logoutSuccessUrl("/") // 配置退出后,进入的申请地址。默认是 loginPage?logout
.logoutUrl("/logout"); // 配置退出登录的门路地址。和页面申请地址统一即可。// 敞开 CSRF 平安协定。// 敞开是为了保障残缺流程的可用。http.csrf().disable();
}
@Bean
public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
角色权限
hasAuthority(String) 判断角色是否具备特定权限
http.authorizeRequests().antMatchers("/main1.html").hasAuthority("admin")
hasAnyAuthority(String …) 如果用户具备给定权限中某一个,就容许拜访
http.authorizeRequests().antMatchers("/admin/read").hasAnyAuthority("xxx","xxx")
hasRole(String) 如果用户具备给定角色就容许拜访。否则呈现 403
// 申请地址为 /admin/read 的申请,必须登录用户领有 '管理员' 角色才可拜访
http.authorizeRequests().antMatchers("/admin/read").hasRole("管理员")
hasAnyRole(String …) 如果用户具备给定角色的任意一个,就容许被拜访
// 用户领有角色是管理员 或 访客 能够拜访 /guest/read
http.authorizeRequests().antMatchers("/guest/read").hasAnyRole("管理员", "访客")
hasIpAddress(String) 申请是指定的 IP 就运行拜访
//ip 是 127.0.0.1 的申请 能够拜访 /ip
http.authorizeRequests().antMatchers("/ip").hasIpAddress("127.0.0.1")
403 权限有余页面解决
1. 编写类实现接口AccessDeniedHandler
/**
* @describe 403 权限有余
* @author: AnyWhere
* @date 2021/4/18 20:57
*/
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
throws IOException, ServletException {response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(
"<html>" +
"<body>" +
"<div style='width:800px;text-align:center;margin:auto;font-size:24px'>" +
"权限有余,请分割管理员" +
"</div>" +
"</body>" +
"</html>"
);
response.getWriter().flush();// 刷新缓冲区
}
}
2. 配置类中配置 exceptionHandling
// 配置 403 拜访谬误处理器。http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);/
RememberMe(记住我)
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 配置记住明码
http.rememberMe()
.rememberMeParameter("remember-me") // 批改申请参数名。默认是 remember-me
.tokenValiditySeconds(14*24*60*60) // 设置记住我无效工夫。单位是秒。默认是 14 天
.rememberMeCookieName("remember-me") // 批改 remember me 的 cookie 名称。默认是 remember-me
.tokenRepository(persistentTokenRepository) // 配置用户登录标记的长久化工具对象。.userDetailsService(userSecurity); // 配置自定义的 UserDetailsService 接口实现类对象
}
@Bean
public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
Spring Security 注解
@Secured
角色校验 , 申请到来访问控制单元办法时必须蕴含 XX 角色能力拜访
角色必须增加 ROLE_前缀
@Secured({"ROLE_管理员","ROLE_访客"})
@RequestMapping("/toMain")
public String toMain(){return "main";}
应用注解 @Secured 须要在配置类中增加注解 使 @Secured 注解失效
@EnableGlobalMethodSecurity(securedEnabled = true)
@PreAuthorize
权限测验, 申请到来访问控制单元之前必须蕴含 xx 权限能力拜访,管制单元办法执行前进行角色校验
/**
* [ROLE_管理员, admin:read, admin:write, all:login, all:logout, all:error, all:toMain]
* @PreAuthorize 角色、权限 校验 办法执行前进行角色校验
*
* hasAnyAuthority()
* hasAuthority()
*
* hasPermission()
*
*
* hasRole()
* hasAnyRole()
* */
@PreAuthorize("hasAnyRole('ROLE_管理员 ','ROLE_访客 ')")
@RequestMapping("/toMain")
@PreAuthorize("hasAuthority('admin:write')")
public String toMain(){return "main";}
应用 @PreAuthorize
和@PostAuthorize
须要在配置类中配置注解 @EnableGlobalMethodSecurity 能力失效
@EnableGlobalMethodSecurity(prePostEnabled = true)
@PostAuthorize
权限测验, 申请到来访问控制单元之后必须蕴含 xx 权限能力拜访,管制单元办法执行完后进行角色校验
/**
* [ROLE_管理员, admin:read, admin:write, all:login, all:logout, all:error, all:toMain]
* @PostAuthorize 角色、权限 校验 办法执行后进行角色校验
*
* hasAnyAuthority()
* hasAuthority()
* hasPermission()
* hasRole()
* hasAnyRole()
* */
@PostAuthorize("hasRole('ROLE_管理员 ')")
@RequestMapping("/toMain")
@PreAuthorize("hasAuthority('admin:write')")
public String toMain(){return "main";}
Spring Security 整合 Thymeleaf 进行权限校验
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
Spring Security 中 CSRF
什么是 CSRF?
CSRF(Cross-site request forgery)跨站申请伪造,也被称为“One Click Attack”或者 Session Riding。通过伪造用户申请拜访受信赖站点的非法申请拜访。
跨域:只有网络协议,ip 地址,端口中任何一个不雷同就是跨域申请。
客户端与服务进行交互时,因为 http 协定自身是无状态协定,所以引入了 cookie 进行记录客户端身份。在 cookie 中会寄存 session id 用来辨认客户端身份的。在跨域的状况下,session id 可能被第三方歹意劫持,通过这个 session id 向服务端发动申请时,服务端会认为这个申请是非法的,可能产生很多意想不到的事件。
艰深解释:
CSRF 就是别的网站非法获取咱们网站 Cookie 值,咱们我的项目服务器是无奈辨别到底是不是咱们的客户端,只有申请中有 Cookie,认为是本人的客户端,所以这个时候就呈现了 CSRF。
近期热文举荐:
1.1,000+ 道 Java 面试题及答案整顿(2022 最新版)
2. 劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4. 别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!