@EnableGlobalMethodSecurity分别有prePostEnabled、securedEnabled、jsr250Enabled三个字段,其中每个字段代码一种注解支持,默认为false,true为开启
在同一个应用程序中,可以启用多个类型的注解,但是只应该设置一个注解对于行为类的接口或者类
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
....
}
Security中角色和权限设置保存的方式一样,但是使用方式不一样
管理员/测试人员/普通用户
对应使用的代码是hasRole,授权代码需要加ROLE_前缀,controller上使用时根据需要可以不要加前缀
执行的操作:路径访问权限 功能操作权限
对应使用的代码是hasAuthority,设置和使用时,名称保持一至即可
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if(this.cname.equals("zs")){
return AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN,add,select");
}else{
return AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_TEST,select");
}
}
实际项目中,用户角色权限信息从数据库中获取
@RequestMapping("/user1")
@ResponseBody
public Object user1(){
return SecurityContextHolder.getContext().getAuthentication();
}
@RequestMapping("/user2")
@ResponseBody
public Object user2(HttpServletRequest request){
return request.getUserPrincipal();
}
@RequestMapping("/user3")
@ResponseBody
public Object user3(Principal principal){
return principal;
}
@RequestMapping("/user4")
@ResponseBody
public Object user4(Authentication authentication){
return authentication;
}
安全注解
使用@Secured注解
@Secured
注解是用来定义业务方法的安全配置。在需要安全的方法上指定 @Secured,并且只有那些角色/权限的用户才可以调用该方法。
@Secured
缺点就是不支持Spring EL
表达式。不够灵活。并且指定的角色必须以ROLE_
开头,不可省略。
@Secured("ROLE_ADMIN")
@RequestMapping("/m1")
@ResponseBody
public String m1(){
return "m1";
}
@Secured({"ROLE_TEST"})
@RequestMapping("/m2")
@ResponseBody
public String m2(){
return "m2";
}
@Secured({"ROLE_ADMIN","ROLE_TEST"})
@RequestMapping("/m3")
@ResponseBody
public String m3(){
return "m3";
}
JSR-250注解
比较简单,在指定方法中使用方法注解@RolesAllowed,@DenyAll,@PermitAll
// 全部拒绝访问
@DenyAll
@RequestMapping("/m4")
@ResponseBody
public String m4(){
return "m4";
}
// 可以访问
@PermitAll
@RequestMapping("/m5")
@ResponseBody
public String m5(){
return "m5";
}
// 拥有角色可访问
@RolesAllowed({"ROLE_ADMIN","ROLE_TEST"})
@RequestMapping("/m6")
@ResponseBody
public String m6(){
return "m6";
}
// 可省略ROLE_
@RolesAllowed({"ADMIN","TEST"})
@RequestMapping("/m7")
@ResponseBody
public String m7(){
return "m7";
}
前置注解
该注解更适合方法级的安全,也支持Spring 表达式语言,提供了基于表达式的访问控制
@PreAuthorize("authentication.principal.username == 'tom' ")
@PreAuthorize("hasRole('ROLE_ADMIN')")
// 只能user角色可以访问
@PreAuthorize ("hasAnyRole('user')")
// user 角色或者 admin 角色都可访问
@PreAuthorize ("hasAnyRole('user') or hasAnyRole('admin')")
// 同时拥有 user 和 admin 角色才能访问
@PreAuthorize ("hasAnyRole('user') and hasAnyRole('admin')")
// 限制只能 id 小于 10 的用户
@PreAuthorize("#id < 10")
User findById(int id);
// 只能查询自己的信息
@PreAuthorize("principal.username.equals(#username)")
User find(String username);
// 限制只能新增用户名称为abc的用户
@PreAuthorize("#user.name.equals('abc')")
void add(User user)
// 查询到用户信息后,再验证用户名是否和登录用户名一致
@PostAuthorize("returnObject.name == authentication.name")
@GetMapping("/get-user")
public User getUser(String name){
return userService.getUser(name);
}
// 验证返回的数是否是偶数
@PostAuthorize("returnObject % 2 == 0")
public Integer test(){
// ...
return id;
}
// 指定过滤的参数,过滤偶数
@PreFilter(filterTarget="ids", value="filterObject%2==0")
public void delete(List<Integer> ids, List<String> username)
@PostFilter("filterObject.id%2==0")
public List<User> findAll(){
...
return userList;
}
表达 | 描述 |
---|---|
hasRole([role]) | 返回true当前委托人是否具有指定角色。默认情况下,如果提供的角色不是以“ ROLE_”开头,则会添加该角色。可以通过修改defaultRolePrefixon来自定义DefaultWebSecurityExpressionHandler。 |
hasAnyRole([role1,role2]) | 返回true当前委托人是否具有提供的任何角色(以逗号分隔的字符串列表形式)。默认情况下,如果提供的角色不是以“ ROLE_”开头,则会添加该角色。可以通过修改defaultRolePrefixon来自定义DefaultWebSecurityExpressionHandler。 |
hasAuthority([authority]) | true如果当前主体具有指定的权限,则返回。 |
hasAnyAuthority([authority1,authority2]) | 返回true当前委托人是否具有提供的任何角色(以逗号分隔的字符串列表形式) |
principal | 允许直接访问代表当前用户的主体对象 |
authentication | 允许直接访问Authentication从SecurityContext |
permitAll | 总是评估为 true |
denyAll | 总是评估为 false |
isAnonymous() | 返回true当前委托人是否为匿名用户 |
isRememberMe() | 返回true当前主体是否是“记住我”的用户 |
isAuthenticated() | true如果用户不是匿名的,则返回 |
isFullyAuthenticated() | 返回true如果用户不是匿名或记得,我的用户 |
hasPermission(Object target, Object permission) | 返回true用户是否可以访问给定权限的给定目标。例如,hasPermission(domainObject, 'read') |
hasPermission(Object targetId, String targetType, Object permission) | 返回true用户是否可以访问给定权限的给定目标。例如,hasPermission(1, 'com.example.domain.Message', 'read') |
RBAC(role-Based-access control),一般都是由 3个部分组成,一个是用户,一个是角色 ,一个是资源(菜单,按钮),然后就是 用户和角色的关联表,角色和资源的关联表
核心就是判断当前的用户所拥有的URL是否和当前访问的URL是否匹配。
@Component("rbacConfig")
public class RbacConfig {
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
boolean hasPermission = false;
Object principal = authentication.getPrincipal();// 获取登录用户的详细信息
// 判断是否登录
if (principal instanceof UserDetails) {
// 类型转换
MyUser myUser = (MyUser) principal;
// 获取请求路径
String uri = request.getRequestURI();
// 获取用户权限并判断
......
}
return hasPermission;
}
}
.authorizeRequests()
.anyRequest()
.access("@rbacConfig.hasPermission(request,authentication)")
@rbacService 就是声明的bean对象
@Configuration
public class GlobalCorsConfig {
@Bean
@Order("0")
public CorsFilter corsFilter(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 允许请求带有验证信息
corsConfiguration.setAllowCredentials(true);
// 允许请求域名
corsConfiguration.addAllowedOrigin("http://localhost:8081");
// 允许请求头
corsConfiguration.addAllowedHeader("*");
// 允许请求方式
corsConfiguration.setAllowedMethods(Arrays.asList("POST"));
// 基于路径的苦于配置源
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**",corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
// 跨域资源共享
.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
//处理跨域请求中的Preflight请求
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
中文文档
https://www.kancloud.cn/yunye/axios/234845/
封装axios
import axios from 'axios'
let http = axios.create({
baseURL:"http://localhost:8088",
withCredentials:true,
timeout:5000,
responseType:'json'
})
// 添加请求拦截器
http.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
http.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
export default http
修改引入的axios
import http from './utils/Http'
Vue.prototype.$axios = http
@Component
public class AccessExceptionHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
httpServletResponse.getWriter().println("{\"code\":403,\"message\":\"没有权限访问呀!\",\"data\":\"\"}");
httpServletResponse.getWriter().flush();
}
}
http...
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationError)
.accessDeniedHandler(accessExceptionHandler)
判断是否是ajax请求,ajax请求返回json
@Component
public class AuthenticationError implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
boolean isajax = false;
String accept = httpServletRequest.getHeader("accept");
if (accept != null && accept.indexOf("application/json") != -1)
{
isajax= true;
}
String xRequestedWith = httpServletRequest.getHeader("X-Requested-With");
if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1)
{
isajax= true;
}
if(isajax){
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
httpServletResponse.getWriter().println("{\"code\":500,\"message\":\"用户名密码错误!\",\"data\":\"\"}");
httpServletResponse.getWriter().flush();
}else{
httpServletResponse.sendRedirect("failure");
}
}
}
用户表
1001 zs 123...
角色表
R01 管理员 admin ....
用户角色关联表
1001 R01
模块表
P01 员工管理 /emp
角色模块表
R01 P01
R01 P02
功能表
G01 ADD
G02 SEELCT
角色功能关联表
R01 G01
R01 G02
(模块功能表)
前台访问后台接口获取数据信息,必须把登录用户的信息随着请求传递给后台接口
每一次请求响应都需要携带用户信息。
Token
后台:JWT
前台:VUEX,sessionstorage,localstrorage
权限控制
后台:注解,RBAC
前台:
动态路由