Spring Security简介
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。
Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求。
Spring Security工作流程
1、登录的时候把,后台用一个类对象将用户信息封装起来,通常使用的是UsernamePasswordAuthenticationToken这个类。
2、执行接口的时候把有权限执行请求url的角色按照要求存起来。
3、对比 访问当前url所需要的权限和 当前用户的信息(权限),最后决定用户是否可以访问这个url。
数据库搭建
MySQL数据库。用户角色与权限管理细分为5个表——用户表、角色表、权限表、用户-角色表、角色-权限表。
工具包
<!--Security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--getter setter注解依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
具体实现步骤
这里只写Spring Securtiy步骤,DAO、Service等业务逻辑步骤省略
1、实现UserDetails接口和UserDetailsService接口(存储用户信息)
在我们的程序中,必须要有一个类,实现UserDetailsService这个接口并且重写它的loadUserByUsername(String s)这个方法。另外也必须要有一个类,实现UserDetails接口并重写里面的getAuthorities()方法。
写一个类继承UserDetails接口,重写里面getAuthorities()方法,这里用到了@Data 注解免去了手动加getter setter
@Data //getter setter注解
public class Suser implements UserDetails {
private String username;
private String password;
//包含着用户对应的所有Role,在使用时调用者给对象注入roles
private List<Srole> srole;
//重写getAuthorities方法
//返回用户所有角色的封装,一个Srole对应一个GrantedAuthority
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
for (Srole role : srole) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getUsername() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
}
写一个类继承UserDetailsService接口,重写loadUserByUsername(String s)方法,我们在写UserService时直接实现这个接口就可以。所以UserService跟其他Service有些不同。
@Service
public class HrService implements UserDetailsService {
@Autowired
private SuserDao suserdao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Suser su = suserdao.UserByUsername(s);
if (su == null) {
throw new UsernameNotFoundException("用户名不对");
}
return su;
}
}
2、实现FilterInvocationSecurityMetadataSource接口(把有权限执行请求url的角色按照要求存起来)
作用是在用户请求一个地址的时候,截获这个地址,告诉程序访问这个地址需要哪些权限角色。
@Component
public class CustomMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
SpermissionService spermissionservice;//权限service
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object o) {
//得到用户的请求地址,控制台输出一下
String requestUrl = ((FilterInvocation) o).getRequestUrl();
System.out.println("用户请求的地址是:" + requestUrl);
//将resource所需要到的role按框架要求封装返回(MenuService里面的查询出所有的权限)
List<Spermission> permission= spermissionservice.getAllMenu();
for (Spermission spn : permission) {
if (antPathMatcher.match(spn.getPage(), requestUrl)
&&spn.getSrole().size()>0) {
List<Srole> roles = spn.getRoles();
int size = roles.size();
String[] values = new String[size];
for (int i = 0; i < size; i++) {
values[i] = roles.get(i).getName();
}
return SecurityConfig.createList(values);
}
}
//没有匹配上的资源,都是登录访问
return SecurityConfig.createList("ROLE_LOGIN");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return FilterInvocation.class.isAssignableFrom(aClass);
}
}
3、实现AccessDecisionManager接口(对比当前用户角色和访问URL所需要的角色,决定是否有权访问)
@Component
public class UrlAccessDecisionManager implements AccessDecisionManager {
@Override
//第一个参数是当前登录用户的信息(角色),第三个参数是可以访问该url的角色
public void decide(Authentication auth, Object o, Collection<ConfigAttribute> cas){
Iterator<ConfigAttribute> iterator = cas.iterator();
while (iterator.hasNext()) {
ConfigAttribute ca = iterator.next();
//当前请求需要的权限
String needRole = ca.getAttribute();
if ("ROLE_LOGIN".equals(needRole)) {
if (auth instanceof AnonymousAuthenticationToken) {
throw new BadCredentialsException("未登录");
} else
return;
}
//当前用户所具有的权限
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
for (GrantedAuthority authority : authorities) {
//对比当前用户角色(权限)和 访问URL角色(权限)
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("权限不足!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
4、实现AccessDeniedHandler接口(作用是自定义403响应内容)
@Component
public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler {
@Override
//自定义403响应内容
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse resp,
AccessDeniedException e) throws IOException {
resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
resp.setContentType("application/json;charset=UTF-8");
PrintWriter out = resp.getWriter();
RespBean error = RespBean.error("权限不足,请联系管理员!");
out.write(new ObjectMapper().writeValueAsString(error));
out.flush();
out.close();
}
}
5、继承WebSecurityConfigurerAdapter类(这是Spring Security的一个配置类)
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
//根据一个url请求,获得访问它所需要的roles权限
@Autowired
MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;
//接收一个用户的信息和访问一个url所需要的权限,判断该用户是否可以访问
@Autowired
MyAccessDecisionManager myAccessDecisionManager;
//403页面
@Autowired
MyAccessDeniedHandler myAccessDeniedHandler;
/**定义认证用户信息获取来源,密码校验规则等*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/**有以下几种形式,使用第3种*/
//inMemoryAuthentication 从内存中获取
//auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("user1").password(new BCryptPasswordEncoder().encode("123123")).roles("USER");
//jdbcAuthentication从数据库中获取,但是默认是以security提供的表结构
//usersByUsernameQuery 指定查询用户SQL
//authoritiesByUsernameQuery 指定查询权限SQL
//auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery(query).authoritiesByUsernameQuery(query);
//注入userDetailsService,需要实现userDetailsService接口
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
//在这里配置哪些页面不需要认证
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/", "/noAuthenticate");
}
/**定义安全策略*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //配置安全策略
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
o.setAccessDecisionManager(myAccessDecisionManager);
return o;
}
})
// .antMatchers("/hello").hasAuthority("ADMIN")
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
StringBuffer sb = new StringBuffer();
sb.append("{\"status\":\"error\",\"msg\":\"");
if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
sb.append("用户名或密码输入错误,登录失败!");
} else if (e instanceof DisabledException) {
sb.append("账户被禁用,登录失败,请联系管理员!");
} else {
sb.append("登录失败!");
}
sb.append("\"}");
out.write(sb.toString());
out.flush();
out.close();
}
})
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
// ObjectMapper objectMapper = new ObjectMapper();
String s = "{\"status\":\"success\",\"msg\":" + "}";
out.write(s);
out.flush();
out.close();
}
})
.and()
.logout()
.permitAll()
.and()
.csrf()
.disable()
.exceptionHandling()
.accessDeniedHandler(myAccessDeniedHandler);
}
}