本文讲述spring Boot整合Spring Security在方法上使用注解实现权限控制,使用自定义UserDetailService,从MySQL中加载用户信息。使用Security自带的MD5加密,对用户密码进行加密。
spring security 的验证流程:
- 用户发出请求
- 过滤器拦截(OauthAuthenticationFilter:doFilter)
- 取得请求资源所需权限(UserDetailService.loadUserByUsername)
- 匹配用户拥有权限和请求权限(AuthenticationProvider authenticate()),如果用户没有相应的权限,
执行第5步,否则执行第6步。 - 登录
- 验证并授权(MyUserDetailServiceImpl:loadUserByUsername)
1、配置类 Spring Security
- 通过 @EnableWebSecurity 注解开启Spring Security的功能
- 继承 WebSecurityConfigurerAdapter ,并重写它的方法来设置一些web安全的细节
- configure(HttpSecurity http) 方法
- 自定义成功/失败的 handle
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private FindByIndexNameSessionRepository<ExpiringSession> sessionRepository;
@Resource
private AdminUserDetailsService adminUserDetailsService;
@Resource
private DaoAuthenticationProvider daoAuthenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
SessionManagementConfigurer<HttpSecurity> sessionManagement = http.sessionManagement();
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
sessionManagement.sessionFixation().migrateSession();
http.csrf().disable();
http.authorizeRequests().antMatchers("/*").permitAll()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll();
http.exceptionHandling().accessDeniedHandler(new JsonAccessDeniedHandler(BaseExceptionEnum.ACCESS_DENIED.getMessage(), BaseExceptionEnum.ACCESS_DENIED.getCode()));
http.logout().logoutUrl("/api/adminUser/logout").logoutSuccessHandler(new JsonLogoutSuccessHandler(BaseExceptionEnum.SUCCESS.getMessage(), BaseExceptionEnum.SUCCESS.getCode()));
http.formLogin().loginPage("/api/adminUser/login").usernameParameter("phone")
.successHandler(new JsonAuthenticationSuccessHandler(BaseExceptionEnum.SUCCESS.getMessage(), BaseExceptionEnum.SUCCESS.getCode()))
.failureHandler(new JsonAuthenticationFailureHandler(BaseExceptionEnum.INVALID_ACCOUNT_PASSWORD.getMessage(), BaseExceptionEnum.INVALID_ACCOUNT_PASSWORD.getCode()));
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
/**
* 配置数据库校验逻辑:手机号码+密码
*
* @return
*/
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
// 设置用户查询处理逻辑的类
daoAuthenticationProvider.setUserDetailsService(adminUserDetailsService);
// 设置密码加密的盐值
// ReflectionSaltSource saltSource = new ReflectionSaltSource();
// saltSource.setUserPropertyToUse("username");
// daoAuthenticationProvider.setSaltSource(saltSource);
// 设置密码加密算法
ShaPasswordEncoder shaPasswordEncoder = new ShaPasswordEncoder(512);
daoAuthenticationProvider.setPasswordEncoder(shaPasswordEncoder);
return daoAuthenticationProvider;
}
@Bean
@Autowired
public SpringSessionBackedSessionRegistry springSessionBackedSessionRegistry(FindByIndexNameSessionRepository sessionRepository) {
return new SpringSessionBackedSessionRegistry(sessionRepository);
}
}
2、自定义 UserDetailService
- 检索使用自己选择的持久化策略的认证信息
- 在 loadUserByUsername() 获取数据库用户信息,并添加相应的权限到 UserDetails
@Service
public class AdminUserDetailsService implements UserDetailsService {
@Resource private AdminUserMapper adminUserMapper;
@Resource private CustomAdminUserMapper customAdminUserMapper;
@Resource private AdminManagementService adminManagementService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AdminUserExample example = new AdminUserExample();
example.createCriteria().andPhoneEqualTo(username);
List<AdminUser> list = adminUserMapper.selectByExampleWithBLOBs(example);
if (list == null || list.size() == 0) {
throw new JrdException(JrdExceptionEnum.ADMIN_NOT_FOUND);
}
AdminUser adminUser = list.get(0);
Set<GrantedAuthority> adminAuthorities = adminManagementService.getAdminAuthorities(adminUser);
Boolean accountNonLocked = true;
if (adminUser.getUserStatus() != null)
accountNonLocked = UserStatusEnum.isUserAccountEnabled(adminUser.getUserStatus());
if (adminUser == null) {
throw new UsernameNotFoundException(AdminUserExceptionEnum.ADMIN_USER_NOT_FOUND.getMessage());
}
return new AdminUserDetails(adminUser,adminAuthorities);
}
}
3、用声明式注解 @PreAuthorize 修饰 Controller 方法
这里有两种用法:
- @PreAuthorize(“hasAuthority(‘authorized’,’ROLE_XXX)”)
- @PreAuthorize(“hasPermission(‘authorized’, ”)”)
例如:
@RequestMapping(value = "/saveAccount", method = RequestMethod.POST)
@PreAuthorize("hasPermission('authorized', '')")
public String saveAccount(@Validated(Save.class) User user) {
logger.info(user.getUsername() + " " + user.getCompanyName());
userService.saveUser(user);
return ApiJsonSerializeUtil.jsonResponse(user);
}
第一种 hasAuthority : spring security 会从 UserDetail 里面 getAuthorities() 里拿出该用户对应的权限,然后再自动帮我们校验;
第二种 hasPermission : 需要配置PermissionEvaluator 、GlobalMethodSecurityConfiguration 两个类。在 PermissionEvaluator 中的 hasPrivilege() 方法中进行权限校验。
@Component
public class AdminPermissionEvaluator implements PermissionEvaluator {
public static final String AUTHORIZED = "AUTHORIZED"; // 要求登录
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
if (authentication == null || !(permission instanceof String)) {
return false;
}
return hasPrivilege(authentication, targetDomainObject.toString(), permission.toString().toUpperCase());
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
return hasPermission(authentication, null, permission);
}
private Boolean hasPrivilege(Authentication authentication, String targetType, String permission) {
if (authentication instanceof AnonymousAuthenticationToken) {
return false;
}
AdminUserDetails principal = (AdminUserDetails) authentication.getPrincipal();
Set<GrantedAuthority> authorities = (Set<GrantedAuthority>) principal.getAuthorities();
if (!permission.equals("")){
for (GrantedAuthority authoritie : authorities){
if (authoritie.getAuthority().equals(permission)){
return true;
}
}
return false;
}
if (authentication.getPrincipal() != null && authentication.getPrincipal() instanceof AdminUserDetails) {
return true;
} else {
return false;
}
}
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class AdminSecurityMethodConfig extends GlobalMethodSecurityConfiguration {
@Resource private AdminPermissionEvaluator adminPermissionEvaluator;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(adminPermissionEvaluator);
return expressionHandler;
}
}