什么是 Spring Security
Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于 Spring 的应用程序的实际标准。
Spring Security 是一个框架,致力于为 Java 应用程序提供身份验证和授权。与所有 Spring 项目一样,Spring Security 的真正强大之处在于可以轻松扩展以满足自定义要求
官方网站:https://spring.io/projects/spring-security#learn
初识 Security
因为之前已经接触过其他的权限框架:shiro 所以很好入门
比较好的入门博文:
Springboot + Spring Security 实现前后端分离登录认证及权限控制
Springboot 集成 SpringSecurity 附代码
关于 SpringBoot 应用中集成 Spring Security 你必须了解的那些事
Spring Boot Security
具体代码实现
通常都是通过自定义 UserDetailsService, AuthenticationProvider, AuthenticationManager,UsernamePasswordAuthenticationFilter 其中的一种来实现的。
最常见的是通过 UserDetailsService 方式来实现,方便快捷
1 引入 Security 包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
2 创建 Security 配置类
/** * @author 海加尔金鹰 www.hjljy.cn * @apiNote websecurtiy权限校验处理 * @since 2020/9/11 **/ @Configuration @EnableWebSecurity @EnableGlobalAuthentication public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { /** * 描述: * http方式走 Spring Security 过滤器链,在过滤器链中,给请求放行,而web方式是不走 Spring Security 过滤器链。 * 通常http方式用于请求的放行和限制,web方式用于放行静态资源 **/ @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() //用于配置直接放行的请求 .antMatchers("/login").permitAll() //其余请求都需要验证 .anyRequest().authenticated() //授权码模式需要 会弹出默认自带的登录框 .and().httpBasic() //禁用跨站伪造 .and().csrf().disable(); //如果项目没有前后端分离,还可以通过 formlogin配置登录相关的页面和请求处理 // 使用自定义的认证过滤器 // http.addFilterBefore(new MyLoginFilter(authenticationManager()),UsernamePasswordAuthenticationFilter.class); } /** * 描述: 静态资源放行,这里的放行,是不走 Spring Security 过滤器链 **/ @Override public void configure(WebSecurity web) { // 可以直接访问的静态数据 web.ignoring() .antMatchers("/css/**") .antMatchers("/404.html") .antMatchers("/500.html") .antMatchers("/html/**") .antMatchers("/js/**"); } /** * 描述:设置授权处理相关的具体类以及加密方式 */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); // 设置不隐藏 未找到用户异常 provider.setHideUserNotFoundExceptions(true); // 用户认证service - 查询数据库的逻辑 provider.setUserDetailsService(userDetailsService()); // 设置密码加密算法 provider.setPasswordEncoder(passwordEncoder()); auth.authenticationProvider(provider); } /** * 描述: 通过自定义的UserDetailsService 来实现查询数据库用户数据 **/ @Override @Bean protected UserDetailsService userDetailsService() { return new UserDetailsServiceImpl(); } /** * 描述: 密码加密算法 BCrypt 推荐使用 **/ @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 描述: 注入AuthenticationManager管理器 **/ @Override @Bean public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } }
3 创建 UserInfo 类
/** * @author 海加尔金鹰 * @apiNote 用户信息类 主要是提供给验证框架使用,并将用户基本信息保存到这个类 * @since 2020/9/11 **/ public class UserInfo extends User { private static final long serialVersionUID = 1L; /** * 描述: 可以添加自定义的用户属性 * 用户邮箱 **/ private String email; /** * 描述: 用户ID **/ private String userId; public UserInfo(String username, String password, Collection<? extends GrantedAuthority> authorities) { super(username, password, authorities); } public UserInfo(String username, String password, String userId,Collection<? extends GrantedAuthority> authorities) { super(username, password, authorities); this.userId=userId; } public UserInfo(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } }
4 实现 UserDetailsService
/** * @author 海加尔金鹰 * @apiNote 用户具体验证类 * @since 2020/9/11 **/ public class UserDetailsServiceImpl implements UserDetailsService { /** * 这里根据传进来的用户账号进行用户信息的构建 * 通常的做法是 * 1 根据username查询数据库对应的用户信息 * 2 根据用户信息查询出用户权限信息 例如菜单添加权限 sys:menu:add * 3 根据用户账号,密码,权限构建对应的UserDetails对象返回 * 这里实际上是没有进行用户认证功能的,真正的验证是在UsernamePasswordAuthenticationFilter对象当中 * UsernamePasswordAuthenticationFilter对象会自动根据前端传入的账号信息和UserDetails对象对比进行账号的验证 * 通常情况下,已经满足常见的使用常见,不过如果有特殊需求,需要使用自己实现的具体认证方式,可以继承UsernamePasswordAuthenticationFilter对象 * 重写attemptAuthentication 方法和successfulAuthentication方法 * 最后在WebSecurityConfiguration里面添加自己的过滤器即可 * @param username 用户账号 * @return UserInfo * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //TODO 当前使用测试数据进行测试 需要修改成实际的业务逻辑处理 // 不限制用户账号。只要密码是123456就可以通过验证 并添加权限 String password = SecurityUtils.encryptPassword("123456"); SimpleGrantedAuthority authority = new SimpleGrantedAuthority("sys:menu:add"); List<GrantedAuthority> authorities =new ArrayList<>(); authorities.add(authority); UserInfo userInfo =new UserInfo(username,password,authorities); userInfo.setEmail("hjljy@outlook.com"); userInfo.setUserId("11111111"); return userInfo; } }
5 提供前端一个登录接口
由于项目进行前后端分离以及 Security 自带的登录界面不美观,所以需要提供一个登录接口给前端调用,该接口需要在配置当中放行,未授权访问需要授权的请求时,会返回 401 或者 403 状态码,前端可以根据这个进行路由提示处理
@RestController public class LoginController { @Autowired AuthenticationManager authenticationManager; @PostMapping(value = "login") public ResultInfo login(@RequestBody Map<String,String> params) { UserInfo userInfo = SecurityUtils.login(params.get("username"), params.get("password"), authenticationManager); return ResultInfo.success(userInfo); } }
6 SecurityUtils 工具类
@Slf4j public class SecurityUtils { /** * 描述根据账号密码进行调用security进行认证授权 主动调 * 用AuthenticationManager的authenticate方法实现 * 授权成功后将用户信息存入SecurityContext当中 * @param username 用户名 * @param password 密码 * @param authenticationManager 认证授权管理器, * @see AuthenticationManager * @return UserInfo 用户信息 */ public static UserInfo login(String username, String password, AuthenticationManager authenticationManager) throws AuthenticationException { //使用security框架自带的验证token生成器 也可以自定义。 UsernamePasswordAuthenticationToken token =new UsernamePasswordAuthenticationToken(username,password ); Authentication authenticate = authenticationManager.authenticate(token); SecurityContextHolder.getContext().setAuthentication(authenticate); UserInfo userInfo = (UserInfo) authenticate.getPrincipal(); return userInfo; } /** * 获取当前登录的所有认证信息 * @return */ public static Authentication getAuthentication(){ SecurityContext context = SecurityContextHolder.getContext(); return context.getAuthentication(); } /** * 获取当前登录用户信息 * @return */ public static UserInfo getUserInfo(){ Authentication authentication = getAuthentication(); if(authentication!=null){ Object principal = authentication.getPrincipal(); if(principal!=null){ UserInfo userInfo = (UserInfo) authentication.getPrincipal(); return userInfo; } } throw new BusinessException(); } /** * 获取当前登录用户ID * @return */ public static String getUserId(){ UserInfo userInfo = getUserInfo(); return userInfo.getUserId(); } /** * 生成BCryptPasswordEncoder密码 * * @param password 密码 * @return 加密字符串 */ public static String encryptPassword(String password) { BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return passwordEncoder.encode(password); } }
7 登录验证
上述流程完毕之后,可以通过工具发送 HTTP 测试一下登录接口
POST http://localhost:8090/login Content-Type: application/json { "username": "测试账号", "password": 123456 }
成功返回账号信息
{ "code": 0, "msg": "操作成功", "data": { "password": null, "username": "测试账号", "authorities": [ { "authority": "sys:menu:add" } ], "accountNonExpired": true, "accountNonLocked": true, "credentialsNonExpired": true, "enabled": true, "email": "hjljy@outlook.com", "userId": "11111111" } }
总结
总的来说入门还是很简单的,网上的资料也比较多,但是大多数的前后端分离都是自定义登录界面,不是接口分离。还有就是基本上 security 会搭配 oauth2 使用进行权限验证。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于