Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架,是保护基于spring应用的实际标准。
Spring Security专注于为Java应用提供认证和授权,并且可以轻松扩展以满足自定义要求。
Spring Security的核心就是一组可配置的过滤器,请求访问资源之前被这些过滤器层层拦截,按照配置每一个过滤器都对请求执行自身的验证逻辑,通过则到下一个过滤器,如果一直通过,最终到达访问资源,其中任何一个未通过,则被拦截无法到达访问资源。
Spring Securit 直接提供了登录和退出功能,并提供了当前用户信息的模板。
Spring Boot 、Spring Security 、MyBatis
vue-cli 、vue、axios、elementui
{
"authorized":true,//是否已授权
"logined":true,//是否已登录
"message":"信息",//信息
"success":true //是否成功
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
在web应用中,Spring Security配置类需要继承WebSecurityConfigurerAdapter,重写其中的配置方法,主要的核心配置都在配置方法中。
配置密码编码器具体代码
//配置密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
BCryptPasswordEncoder是Spring Security提供的使用哈希算法结合盐值(盐值即一个安全随机数)加密器,该类对同一明文每次加密都不一样,哈希又是一种不可逆算法,所以密码认证时需要使用相同的方式对待校验的明文进行加密,然后比较这两个密文来进行验证。
注入安全Dao的具体代码
//安全Dao负责从数据库中获取认证和授权等数据
private final SecurityDao securityDao;
//构造方法
public SecurityConfig(SecurityDao securityDao) {
this.securityDao = securityDao;
}
SecurityDao并非Spring Security提供的,是自定义的访问数据库的对象。
配置UserDetailsService对象的具体代码
UserDetailsService接口由Spring Security 提供,其作用为根据用户名(账号)加载当前用户信息,定义如下:
public interface UserDetailsService{
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
}
当前用户信息UserDetials也是Spring Security 提供的接口 ,定义如下:
public interface UserDetails{
public Collection<? extends GrantedAuthority> getAuthorities(); //获取当前用户的权限
public String getPassword(); //获取当前用户的密码,此处获取的密码应当加密形式
public String getUsername(); //获取当前用户名(账号)
public boolean isAccountNonExpired(); //当前用户是否未过期
public boolean isAccountNonLocked(); //当前用户是否未锁定
public boolean isCredentialsNonExpired(); //当前用户凭证(密码)是否未过期
public boolean isEnabled(); //当前用户是否可用
}
配置UserDetailsService对象的作用有两个,一个是提供自定义UserDetailsService的实现并作为spring的bean对象,另一个是提供UserDetails 实现,具体代码如下:
//配置UserDetailsService对象,用于用户获取当前用户信息
@Bean
public UserDetailsService userDetailsService(SecurityDao securityDao) {
/*
匿名实现UserDetailsService接口,
该接口中仅有一个方法public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException,
此方法的功能是依据登录用户名(可以理解为账号)加载当前用户信息,UserDetials接口表示当前用户信息
*/
return userId -> {
User user = securityDao.findUserByUserId(userId);//根据账号从数据库中查询用户信息
if (user == null) throw new UsernameNotFoundException("账号不正确!");
//根据账号从数据库中权限编号的集合
List<String> moduleIdList = securityDao.findModuleIdListByUserId(userId);
List<GrantedAuthority> authorityList = new ArrayList<>();//权限集合
String authorityPattern = "ROLE_{0}";//在Spring Security中,权限的名称格式为“ROLE_XXX”
//将权限编号转换为“ROLE_权限编号”的形式,然后封装为SimpleGrantedAuthority对象放入集合中
for (String moduleId : moduleIdList) {
authorityList.add(new SimpleGrantedAuthority(MessageFormat.format(authorityPattern, moduleId)));
}
//CurrUser是一个自定义的类实现了UserDetails接口
return new CurrUser(
user.getU_id(),
user.getU_name(),
user.getU_pwd(),
user.getU_status().equals(DataStatusEnum.已启用.getCode()),
authorityList
);
};
}
上述配置中的CurrUser是自定义的UserDetails实现,具体代码如下:
public class CurrUser implements UserDetails {
private String username;//账号
private String factname;//姓名
private String password;
private boolean enabled;
private Collection<? extends GrantedAuthority> authorities;
public CurrUser() {
}
public CurrUser(String username, String factname, String password, boolean enabled, Collection<? extends GrantedAuthority> authorities) {
this.username = username;
this.factname = factname;
this.password = password;
this.enabled = enabled;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
public String getFactname(){
return factname;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
核心安全配置
重写config(HttpSecurity)方法,其中写核心的安全配置,配置内容概括如下:
(1)通过查询数据库,将模块地址和相应权限编号对应起来,访问某个模块必须要有相应的权限;
(2)其它地址请求需要认证;
(3)配置登录,包括登录处理地址、登录账号密码参数名称、登录成功/失败的响应,以及允许所有请求访问登录相关地址(接口)等;
(4)配置退出,包括退出地址 和 退出成功的响应;
(5)配置异常处理,包括未认证异常和未授权异常
(6)配置允许跨域
(7)配置禁用防CSRF攻击
具体配置代码如下:
//重写WebSecurityConfigurerAdapter的configure(HttpSeeurity)方法,核心的配置都在此方法中
@Override
protected void configure(HttpSecurity http) throws Exception {
//证授权配置-开始
String antUrlPattern = "{0}/**";//地址模式
List<Module> moduleList = securityDao.findModuleList();//获得所有权限
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorize = http.authorizeRequests();
for (Module module : moduleList) {
//为每一个地址模式匹配一个权限(角色)
authorize
.antMatchers(MessageFormat.format(antUrlPattern, module.getM_url()))
.hasRole(module.getM_id().toString());
}
authorize
.anyRequest().authenticated()//其它请求需要认证
.and()//此方法返回ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry对象
//以下登录配置开始
.formLogin()
.loginProcessingUrl("/login")//登录处理地址
.usernameParameter("u_id")//定义登录时,用户名的参数名,默认为 username
.passwordParameter("u_pwd")//定义登录时,密码的参数名,默认为 password
.successHandler((req, resp, authentication) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(JSON.toJSONString(Result.success("登录成功")));
out.flush();
})//登录成功的处理器
.failureHandler((req, resp, exception) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(JSON.toJSONString(Result.fail("登录失败!")));
out.flush();
})//登录失败的处理器
.permitAll();//登录相关访问地址一律放行
//以上登录配置
//认证授权配置-结束
http
//以下退出配置
.logout()
.logoutUrl("/logout")//退出地址
.logoutSuccessHandler((req, resp, authentication) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(JSON.toJSONString(Result.success("您已成功退出系统!")));
out.flush();
})//退出成功的处理器
.permitAll()//退出地址一律放行
//以上退出配置
.and() //返回HttpSecurity对象
//以下异常处理配置
.exceptionHandling()
//未认证用户访问需要认证资源异常处理
.authenticationEntryPoint((httpServletRequest, httpServletResponse, e) -> {
httpServletResponse.setContentType("application;charset=UTF-8");
PrintWriter out = httpServletResponse.getWriter();
out.print(JSON.toJSONString(Result.unlogined()));
out.flush();
})
//认证用户访问资源权限不足异常处理
.accessDeniedHandler((httpServletRequest, httpServletResponse, e) -> {
httpServletResponse.setContentType("application;charset=UTF-8");
PrintWriter out = httpServletResponse.getWriter();
out.print(JSON.toJSONString(Result.unauthorized()));
out.flush();
})
//以上异常处理配置
.and()
.cors()//允许跨域,如果springboot/springmvc已有跨域配置,自动采用springboot/springmvc跨域配置
.and()
//禁用防CSRF攻击
.csrf().disable();
}
在spring mvc中,可以通过在控制器处理方法上定义Principal类型的参数获得经过认证的当前用户信息,示例如下:
@GetMapping("/curruser")
public CurrUser currUser(Principal principal){
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken)principal;
CurrUser currUser = (CurrUser) token.getPrincipal();
return currUser;
}
后端项目代码
前端项目代码