spring security
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
底层原理
过滤链
FilterSecurityInterceptor:方法级别的权限过滤器,基本位于最底层
ExceptionTranslationFilter:异常过滤器
UsernamePasswordAuthenticationFilter:校验用户名密码过滤器
过滤器加载
.....
两大接口
用于自定义开发
UsewrDetailsService接口:
查询数据库用户名和密码过程
继承UsernamePasswordAuthenticationFilter,重写里面的attempAuthenticationFilter方法进行验证,然后重写该接口父类中的successAuthentication方法和unsuccessAuthentication方法。
创建件一个类实现userDetailsService接口,编写查询数据库过程,返回一个User对象。这个User对象是安全框架提供的对象。
passwordEncoder接口: 实现类BCryptPasswordEncoder()
数据加密接口,用于返回user对象那个里面密码加密
实践
1.设置登录的用户名和密码
-
配置文件
spring: security: user: name: xxxx password: xxxxx -
配置类
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String password = passwordEncoder.encode("123"); auth.inMemoryAuthentication().withUser("tom").password(password).roles("admin"); } @Bean PasswordEncoder password() { return new BCryptPasswordEncoder(); } } -
自定义编写实现类
-
第一步 创建配置类,设置使用哪个userdetailsService实现类
@Configuration public class MySecurityConfig 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(); } } -
第二步 编写实现类,返回user对象,user对象有用户名密码操作
@Service("userDetailsService") public class MyUserDetailService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role"); return new User("li", new BCryptPasswordEncoder().encode("123"), auths); } }
-
测试
@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<com.zero.entity.User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
com.zero.entity.User user = userMapper.selectOne(wrapper);
if(user == null) {
throw new UsernameNotFoundException("用户名找不到");
}
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(user.getUsername(), new BCryptPasswordEncoder().encode(user.getPassword()), auths);
}
}
配置自定义认证页面
@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防护
}
授权
hasAuthority方法:当前主体有指定的权限
1.配置类设置当前访问路径有哪些权限
.antMatchers("test/index").hasAuthority("admins")//拥有admins权限才可以访问
hasAnyAuthority方法:拥有多个权限
.antMatchers("/test/index").hasAnyAuthority("admins,manager") //拥有多个权限中的一个就可以访问
hasRole方法:拥有角色
hasAnyRole方法:拥有角色中的一个
自定义无权限页面
http.exceptionHandling().accessDeniedPage("/unauth.html");//设置无权限页面
认证相关注解
@secured:用户具有某个角色,可以访问
-
使用@EnableGlobalMethodSecurity(securedEnabled=true)开启注解
-
在controller方法上使用注解,设置角色
-
@GetMapping("/update") @Secured({"ROLE_sale","ROLE_manager"}) //只有拥有这两种角色才能访问这个方法 public String update() { return "update"; }
@PreAuthorize:方法进入前进行权限验证
- 启动类上开启
- EnableGlobalMethodSecurity(prepostenabled=true)开启
@PreAuthorize("hasAnyAuthority('admins')")
public String update() {
return "update";
}
@postAuthorize:方法完成后进行验证,一般验证返回值
-
启动类上开启
-
EnableGlobalMethodSecurity(prepostenabled=true)开启
@PreAuthorize("hasAnyAuthority('admins')")
public String update() {
return "update";
}
postfilter:返回的数据过滤
prefilter:传入方法数据过滤
@PostFilter("filterObject.username=='wang'")
public List<User> update() {
System.out.println("test");
List<User> list = new ArrayList<>();
list.add(new User(1,"wang","123"));
list.add(new User(12,"admin2","1234"));
return list;
}
用户注销
//退出
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
认证流程原理:
usernamepasswordAuthennticationFilter【继承的是AbstractAuthenticationProcessingFilter ,如果自己要自定义认证filter,继承这个即可】,将表单用户信息封装为authentication(其实现类为usernamepasswordauthenticationtoken)然后交给authenticationmanager认证authentication,委托给absauthentication进行验证(在daoauthentication获取userdatail信息,然后校验密码填充authentication),返回authtication,通过securirycontextholder.getcontext(),setauthentication()将authentication保存至上下文。
授权流程(web授权,方法授权)
1.拦截请求。已经认证的用户访问受保护的资源将被securityFilterChain中的FilterSecurityInterceptor的子类拦截。
2.获取资源管理访问策略,FilterSecurityInterceptor会从SecurityMetadataSource子类DefaultFilterSecurityMetadataSource获取要访问当前资源所需要的权限Collection.
3.最后,FilterSecurityInterceptor会调用AccessDecisionManager进行授权决策,决定是否允许访问。
自动登录
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
./bin/mysqld --initialize --user=mysql --basedir=/usr/local/mysql --\
datadir=/usr/local/mysql/data
原理:
认证
在usernamePasswordAuthenticationFilter通过后,调用remebermeservices,进行业务逻辑操作,将token写入浏览器同时也要保存到数据库中,下次访问的时候,通过 remembermeauthenticationfilter进行过滤,然后调用autologin。就会将浏览器中带的token和数据中的token进行比对,实现自动登录
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}
protected void onLoginSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication successfulAuthentication) {
String username = successfulAuthentication.getName();
logger.debug("Creating new persistent login for user " + username);
PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
username, generateSeriesData(), generateTokenData(), new Date());
try {
tokenRepository.createNewToken(persistentToken);
addCookie(persistentToken, request, response);
}
catch (Exception e) {
logger.error("Failed to save persistent token ", e);
}
}