Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring的IOC,DI,AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为安全控制编写大量重复代码的工作。
最基本的对象,保存着当前会话用户认证,权限,鉴权等核心数据。SecurityContextHolder默认使用ThreadLocal策略来存储认证信息,与线程绑定的策略。用户退出时,自动清除当前线程的认证信息。
初始化源码:明显使用ThreadLocal线程。
private static void initialize() { if (!StringUtils.hasText(strategyName)) { strategyName = "MODE_THREADLOCAL"; } if (strategyName.equals("MODE_THREADLOCAL")) { strategy = new ThreadLocalSecurityContextHolderStrategy(); } else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) { strategy = new InheritableThreadLocalSecurityContextHolderStrategy(); } else if (strategyName.equals("MODE_GLOBAL")) { strategy = new GlobalSecurityContextHolderStrategy(); } else { try { Class<?> clazz = Class.forName(strategyName); Constructor<?> customStrategy = clazz.getConstructor(); strategy = (SecurityContextHolderStrategy)customStrategy.newInstance(); } catch (Exception var2) { ReflectionUtils.handleReflectionException(var2); } } ++initializeCount; }
源码
public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean var1) throws IllegalArgumentException; }
源码分析
1)、getAuthorities,权限列表,通常是代表权限的字符串集合;
2)、getCredentials,密码,认证之后会移出,来保证安全性;
3)、getDetails,请求的细节参数;
4)、getPrincipal, 核心身份信息,一般返回UserDetails的实现类。
封装了用户的详细的信息。
public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); }
实现该接口,自定义用户认证流程,通常读取数据库,对比用户的登录信息,完成认证,授权。
public interface UserDetailsService { UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException; }
认证流程顶级接口。可以通过实现AuthenticationManager接口来自定义自己的认证方式
Spring提供了一个默认的实现,ProviderManager。
public interface AuthenticationManager { Authentication authenticate(Authentication var1) throws AuthenticationException; }
1)、模拟增删改查 2)、未登录授权都不可以访问 3)、登录后根据用户权限,访问指定页面 4)、对于未授权页面,访问返回403:资源不可用
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
package com.Boot.config; import com.Boot.Security.UserDetailServiceImpl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; /** * @Classname SpringSecurityConfig * @Description TODO * @Date 2019/11/6 19:59 * @Created by 远 */ @Configuration @EnableWebSecurity //启动springSecurity启动链 public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { /** * @return * @Author 远 * @Description 代替认证管理器 * @Param **/ /** * 自定义认证数据源 */ @Override protected void configure(AuthenticationManagerBuilder builder) throws Exception{ builder.userDetailsService(userDetailService()) .passwordEncoder(passwordEncoder()); } @Bean public UserDetailServiceImpl userDetailService (){ return new UserDetailServiceImpl () ; } /** * 密码加密 */ @Bean public BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } /** * @Author 远 * @Description 硬编码一个用户用于测试 * @Param * @return **/ /*@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("ljw").password("123").authorities("ROLE_ADD"); }*/ /** * @return * @Author 远 * @Description 代替之前配置<security:http></security:http> **/ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //antMatchers指定页面,hasAnyAuthority指定拥有什么样的权限的用户可以访问 .antMatchers("/add").hasAnyAuthority("ROLE_ADD") .antMatchers("/update").hasAnyAuthority("ROLE_UPDATE") .antMatchers("/list").hasAnyAuthority("ROLE_SELECT") .antMatchers("/delete").hasAnyAuthority("ROLE_DELETE") //放行index和login页面 .antMatchers("/login").permitAll() .antMatchers("/index").permitAll() //拦截全部 .antMatchers("/**") .fullyAuthenticated() .and() //解决页面csrf报错 .csrf().disable(); // 配置登录功能 http.formLogin().usernameParameter("user") .passwordParameter("pwd") .loginPage("/userLogin"); // 注销成功跳转首页 http.logout().logoutSuccessUrl("/"); //开启记住我功能 http.rememberMe().rememberMeParameter("remeber"); } }
package com.Boot.Security; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; /** * @Classname UserDetailServiceImpl * @Description 认证 * @Date 2019/11/6 22:22 * @Created by 远 */ @Service public class UserDetailServiceImpl implements UserDetailsService { @Resource private UserRoleMapper userRoleMapper ; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 这里可以捕获异常,使用异常映射,抛出指定的提示信息 // 用户校验的操作 // 假设密码是数据库查询的 123 String password = "$2a$10$XcigeMfToGQ2bqRToFtUi.sG1V.HhrJV6RBjji1yncXReSNNIPl1K"; // 假设角色是数据库查询的 //查询数据里所有的用户对应角色的权限 List<String> roleList = userRoleMapper.selectByUserName(username) ; List<GrantedAuthority> grantedAuthorityList = new ArrayList<>() ; /* * Spring Boot 2.0 版本踩坑 * 必须要 ROLE_ 前缀, 因为 hasRole("LEVEL1")判断时会自动加上ROLE_前缀变成 ROLE_LEVEL1 , * 如果不加前缀一般就会出现403错误 * 在给用户赋权限时,数据库存储必须是完整的权限标识ROLE_LEVEL1 */ if (roleList != null && roleList.size()>0){ for (String role : roleList){ //将所有的角色遍历到GrantedAuthority grantedAuthorityList.add(new SimpleGrantedAuthority(role)) ; } } //返回用户,用户的用户名,密码,以及权限 return new User(username,password,grantedAuthorityList); } }
package com.Boot.Controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; /** * @Classname productController * @Description 增删改查 * @Date 2019/11/1 14:41 * @Created by 远 */ @Controller public class productController { /** * @Author 远 * @Description * @Param * @return **/ @RequestMapping("/add") public String add() { return "product/add"; } /** * @Author 远 * @Description 商品修改 **/ @RequestMapping("update") public String Update() { return "product/update"; } /** * @Author 远 * @Description 商品显示 **/ @RequestMapping("list") public String list() { return "product/list"; } /** * @Author 远 * @Description 商品删除 **/ @RequestMapping("delete") public String del() { return "product/delete"; } @RequestMapping("/login") public String login(){ return "login"; } @RequestMapping("index") public String index(){ return"index"; } @RequestMapping("403") public String forbidden(){ return "403"; } }
这里要和Security的配置文件相对应。
<div align="center"> <form th:action="@{/userLogin}" method="post"> 用户名:<input name="user"/><br> 密 码:<input name="pwd"><br/> <input type="checkbox" name="remeber"> 记住我<br/> <input type="submit" value="Login"> </form> </div>