Security
Security可以概括为两个功能
认证:用户能否登录
授权:用于判断用户能否调用某个接口
1、Security认证
SecurityConfig配置类
创建SecurityConfig配置类,通过configure(HttpSecurity http)函数完成认证。
package com.komorebi.config;
import com.komorebi.service.SecurityUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//密码策略配置
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
@Autowired
private SecurityUserService securityUserService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.userDetailsService(securityUserService);
http.authorizeRequests() //开启登录认证
// .antMatchers("/user/findAll").hasRole("admin") //访问接口需要admin的角色
.antMatchers("/css/**").permitAll()
.antMatchers("/img/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/plugins/**").permitAll()
//AuthService类被加入了ioc容器中,默认名为首字母小写,所以@authService就是取该类
.antMatchers("/admin/**").access("@authService.auth(request,authentication)") //自定义service 来去实现实时的权限认证
//authenticated()表示只要是认证的用户都可以访问,即登陆成功的用户都可访问
.antMatchers("/pages/**").authenticated()
.and().formLogin()
.loginPage("/login.html") //自定义的登录页面
.loginProcessingUrl("/login") //登录处理接口
.usernameParameter("username") //定义登录时的用户名的key 默认为username
.passwordParameter("password") //定义登录时的密码key,默认是password
.defaultSuccessUrl("/pages/main.html")
.failureUrl("/login.html")
.permitAll() //通过 不拦截,更加前面配的路径决定,这是指和登录表单相关的接口 都通过
.and().logout() //退出登录配置
.logoutUrl("/logout") //退出登录接口
.logoutSuccessUrl("/login.html")
.permitAll() //退出登录的接口放行
.and()
.httpBasic()
.and()
.csrf().disable() //csrf关闭 如果自定义登录 需要关闭
.headers().frameOptions().sameOrigin();
}
}
Security数据库认证登录
认证是用来判断某个用户能否登录,所以实质是认证登录,一般通过数据库认证登录。
1)定义SecurityService类,并实现UserDetailService接口,实现类中方法。
2)当用户登录时,springSecurity就会将请求转发到SecurityService,之后根据用户名查找用户,不存在就抛出异常,存在就将用户名、密码、授权列表组装成springSecurity的User对象,并返回给Security框架。
SecurityService类
该类中最用的就是用户授权列表:
List< GrantedAuthority > authorityList = new ArrayList<>()。
其中GrantedAuthority 是接口,后续像列表中添加对象,我们添加的是它的实现类。
package com.komorebi.service;
import com.komorebi.pojo.Admin;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@Component
@Slf4j
public class SecurityService implements UserDetailsService{
@Autowired
private AdminService adminService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("username:{}",username);
//当用户登录的时候,springSecurity 就会将请求 转发到此
//根据用户名 查找用户,不存在 抛出异常,存在 将用户名,密码,授权列表 组装成springSecurity的User对象 并返回
Admin adminUser = adminService.findAdminByUserName(username);
if (adminUser == null){
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> authorityList = new ArrayList<>();
//查询角色和角色对应的权限 并赋予当前的登录用户,并告知spring security框架
List<Role> roleList = roleMapper.findRoleListByUserId(adminUser.getId());
for (Role role : roleList) {
List<Permission> permissionList = permissionMapper.findPermissionByRole(role.getId());
authorityList.add(new MySimpleGrantedAuthority("ROLE_"+role.getRoleKeyword()));
for (Permission permission : permissionList) {
authorityList.add(new MySimpleGrantedAuthority(permission.getPermissionKeyword(),permission.getPath()));
}
}
UserDetails userDetails = new User(username,adminUser.getPassword(),authorityList);
//将用户密码告诉springSecurity
//剩下的认证 就由框架Security帮我们完成
return userDetails;
}
}
上面代码中,对于授权分两步,查询用户对应的角色,查询角色对应的权限,然后都存入权限列表中。特别注意的是:为了区分角色和具体权限,我们在存取角色时,要对角色进行“ROLE_”拼接,当然也可以在设置数据库时就将该角色关键字字段的值加上这个前缀。
Security认证时才不管你是角色,还是权限。它只比对字符串。比如它有个表达式hasRole(“ADMIN”)。那它实际上查询的是用户权限集合中是否存在字符串”ROLE_ADMIN”。如果你从角色表中取出用户所拥有的角色时不加上”ROLE_”前缀,那验证的时候就匹配不上了。所以角色信息存储的时候可以没有”ROLE_”前缀,但是包装成GrantedAuthority对象的时候必须要有。
GrantedAuthority是一个接口,我们在权限列表中存储的应该是它的实现类,但是它的实现类只能存储权限或者角色关键字,但是我们授权在这里是根据请求路径做的,所以我们只需要自己写一个GrantedAuthority的实现类,然后增加一个路径参数即可。
这样我们存储时,对于角色只需要:
authorityList.add(new MySimpleGrantedAuthority(“ROLE_”
+role.getRoleKeyword()));
对于权限只需要
authorityList.add(new MySimpleGrantedAuthority(
permission.getPermissionKeyword(),permission.getPath()));
自己创建的GrantedAuthority 实现类MySimpleGrantedAuthority
public class MySimpleGrantedAuthority implements GrantedAuthority {
private String authority;
private String path;
public MySimpleGrantedAuthority(){}
public MySimpleGrantedAuthority(String authority){
this.authority = authority;
}
//包含请求路径的构造函数
public MySimpleGrantedAuthority(String authority,String path){
this.authority = authority;
this.path = path;
}
@Override
public String getAuthority() {
return authority;
}
public String getPath() {
return path;
}
}
注意:当我们的SecurityService有多个时,我们需要在Security配置类中@Autowired注入,然后http.userDetailsService()完成设置。
@Autowired
private SecurityService securityService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.userDetailsService(securityService);
http.authorizeRequests() //开启登录认证
.antMatchers("/user/findAll").hasRole("admin") //访问接口需要admin的角色
.antMatchers("/css/**").permitAll()
.antMatchers("/img/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/plugins/**").permitAll()
//AuthService类被加入了ioc容器中,默认名为首字母小写,所以@authService就是取该类
//authenticated()表示只要是认证的用户都可以访问,即登陆成功的用户都可访问
.antMatchers("/pages/**").authenticated()
2、Security授权
为什么做授权
当用户登陆成功后,每个页面中都会有许多的功能,并不是每个用户都有权限使用,所以接下来就要做一个用户授权。
如何实现
前面讲的认证可以让我们成功登录,并且我们在SecurityService 类中已经将用户的权限返回给了Security,接下来我们只需要判断用户的权限集合中是否拥有现在想要使用的权限。
前端中我们所看到的功能在后端实际是一个个的请求接口,所以对于授权的实现,就是规定用户是否可以调用某个接口。所以我们可以通过在数据库权限表中添加一个path字段,用url表示,即请求接口的路径,我们只需要判断request的url和我们数据库中用户角色对应的path字段值是否相同即可,相同则放行,不同则没有权限访问,即拦截。
步骤:
1)获得userDetails,userDetails前面认证时已经返回给了Security,所一很容易获得。
Object principal = authentication.getPrincipal();
UserDetails userDetails = (UserDetails) principal;
2)获得权限列表:userDetails.getAuthorities()
3)遍历权限列表,判断请求路径和权限的路径是否相同,相同则放行,否则不放行,请求失败,即没有权限访问。
true代表放行,false代表拦截
package com.komorebi.service;
import com.komorebi.pojo.Admin;
import com.komorebi.pojo.Permission;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@Service
public class AuthService {
@Autowired
private AdminUserMapper adminUserMapper;
@Autowired
private RoleMapper roleMapper;
@Autowired
private PermissionMapper permissionMapper;
public boolean auth(HttpServletRequest request, Authentication authentication){
String requestURI = request.getRequestURI();
Object principal = authentication.getPrincipal();
if (principal == null || "anonymousUser".equals(principal)){
//未登录
return false;
}
UserDetails userDetails = (UserDetails) principal;
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
for (GrantedAuthority authority : authorities) {
MySimpleGrantedAuthority grantedAuthority = (MySimpleGrantedAuthority) authority;
String[] paths = StringUtils.split(requestURI, "?");
if (paths[0].equals(grantedAuthority.getPath())){
return true;
}
}
return false;
}
}
AuthService创建好后,要在SecurityConifg配置类中加入
例如:所有访问admin路径下的请求都会通过AuthService来判断是否能够调用某个接口,即是否有某个功能的权限。其实接口调用就是一条访问路径。
access("@authService.auth(request,authentication)参数名request,authentication是不能改变的,它们要和我们授权方法中参数名保持相同。
.antMatchers("/admin/**").access("@authService.auth(request,authentication)")
参考视频:B站码神之路spring boot整合Securityhttps://www.bilibili.com/video/BV1L5411g7KP?p=28
Security中GrantedAuthority授权如何实现参考链接
https://www.jqhtml.com/49167.html