欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

带你使用Spring Secutity实现权限管理(干货无套话)

程序员文章站 2024-02-29 10:16:16
...


欢迎访问我的个人博客(点击进入)

一.数据表结构

这是基于RBAC模型的数据表结构
带你使用Spring Secutity实现权限管理(干货无套话)

二.实体类

权限管理主要是对于登入用户的身份验证和通过该用户的角色来获取到该用户的权限,并全权交给到SpringSecurity进行管理

用户实体类

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.Date;

@Data
@EqualsAndHashCode(callSuper = true)
public class SysUser extends BaseEntity<Long> {
	private static final long serialVersionUID = -6525908145032868837L;
	private String username;
	private String password;
	private String nickname;
	private String headImgUrl;
	private String phone;
	private String telephone;
	private String email;
	@JsonFormat(pattern = "yyyy-MM-dd")
	private Date birthday;
	private Integer sex;
	private Integer status;
	private String intro;

	public interface Status {
		int DISABLED = 0;
		int VALID = 1;
		int LOCKED = 2;
	}
}

权限对应的实体类

import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
public class SysPermission extends BaseEntity<Integer> {

	private static final long serialVersionUID = -6525908145032868837L;
	private Integer parentId;
	private String name;
	private String css;
	private String href;
	private Integer type;
	private String permission;
	private Integer sort;

	@Override
	public String toString() {
		return "SysPermission{" +
				"parentId=" + parentId +
				", name='" + name + '\'' +
				", css='" + css + '\'' +
				", href='" + href + '\'' +
				", type=" + type +
				", permission='" + permission + '\'' +
				", sort=" + sort +
				'}';
	}
}

他们之间的中间表就不一一列举,需要完整源码的可以到我的github中获取(文末有链接)

三.实现权限管理功能

要实现权限管理功能需要先配置一个Config类,并让它继承WebSecurityConfigurerAdapter接口
实现如下方法:

方法 作用
void configure(AuthenticationManagerBuilder auth) 身份验证管理器,将用户信息交给SpringSecurity管理
void configure(HttpSecurity http) 配置SpringSecurity,设置忽略拦截菜单,自定义登录页,登出和异常处理

代码如下

1.身份验证管理器

	@Resource
    private UserDetailsService userDetailsService;
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

这个方法中传入了参数,userDetailsService,passwordEncoder()

方法 作用
userDetailsService 实现了UserDetailsService接口,配置用户权限
passwordEncoder() 进行密码编码这里使用了BCrypt的编码方式

1.1 userDetailsService配置用户信息

/**
 * @Author: LySong
 * @Date: 2020/3/20 21:35
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Resource
    private PermissionDao permissionDao;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        SysUser sysUser = userService.getUser(s);
        if(sysUser == null){
            throw new AuthenticationCredentialsNotFoundException("用户名不存在");
        }else if(sysUser.getStatus() == SysUser.Status.DISABLED){
            throw new LockedException("用户被锁定,请联系管理员");
        }

        LoginUser loginUser = new LoginUser();
        BeanUtils.copyProperties(sysUser,loginUser);
        List<SysPermission> permissions = permissionDao.listByUserId(sysUser.getId());
        loginUser.setPermissions(permissions);
        return loginUser;
    }
}

实现UserDetailsService方法后,实现了loadUserByUsername(String s)方法,这个方法的入参就是SpringSecurity得到的前端输入的用户名,我们根据用户名查出对应的用户,并进行相应的逻辑处理,再将通过连表查询得到的权限set到LoginUser中。这个方法的返回值是UserDetails类型的,我们需要建一个类。

1.1.1 LoginUser类编写

/**
 * @Author: LySong
 * @Date: 2020/3/20 21:38
 */
@Data
public class LoginUser extends SysUser implements UserDetails {

    private List<SysPermission> permissions;

    @Override
    @JsonIgnore
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return permissions.parallelStream().filter(p -> !StringUtils.isEmpty(p.getPermission()))
                .map(p -> new SimpleGrantedAuthority(p.getPermission())).collect(Collectors.toSet());
    }

    @Override
    @JsonIgnore
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return getStatus() != Status.LOCKED;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

新建的类继承了User类,实现了SpringSecurity的UserDetails接口,并实现了如下方法

方法 作用
Collection<? extends GrantedAuthority> getAuthorities() 这个方法用来得到这个用户对应的权限列表,交给SpringSecurity进行管理
isAccountNonExpired() 账户是否到期,若无该字段设为true
isAccountNonLocked() 账户是否被锁定,若无该字段设为true
isCredentialsNonExpired() 凭证是否过期,若无该字段设为true
isEnabled() 账户是否启用,若无该字段设为true

1.2 密码编码

对密码进行编码

@Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

配置好后,SpringSecuriry将自动对密码进行编码以匹配数据库中的密码

2 配置拦截

经过上面对于用户管理器的配置,我们已经完成了对用户的识别,向SpringSecurity权限列表的过程,接下来我们就对SpringSecurity进行配置,设置放行信息


    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;
	
    @Autowired
    private RestAuthenticationAccessDeniedHandler restAuthenticationAccessDeniedHandler;

    @Autowired
    private MyLogoutSuccessHandler myLogoutSuccessHandler;
 	@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.headers().frameOptions().sameOrigin();
        http.authorizeRequests()
                .antMatchers("/xadmin/**",
                        "/treetable-lay/**",
                        "/ztree/**",
                        "/static/**",
                        "/css/**",
                        "/login.html")
                .permitAll()
                .anyRequest()
                .authenticated();
        http.formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .successHandler(myAuthenticationSuccessHandler)
                .failureHandler(myAuthenctiationFailureHandler)
        .and().logout()
                .permitAll()
                .invalidateHttpSession(true)
                .deleteCookies("JESSIONID")
                .logoutSuccessHandler(myLogoutSuccessHandler);
        //异常处理
        http.exceptionHandling().accessDeniedHandler(restAuthenticationAccessDeniedHandler);
    }
  • http.csrf().disable(); csrf(跨站身份信息伪造)的处理
  • http.headers().frameOptions().sameOrigin(); 处理前端的iframe的错误

http.authorizeRequests():请求拦截

方法 作用
.antMatchers() 设置放行的链接,一般设置为登录页和项目的静态资源
.permitAll() 允许上述所有的请求通过
.anyRequest() 监控所有的请求
.authenticated() 身份验证

http.formLogin():自定义登录

方法 作用
.loginPage("/login.html") 设置登录页
.loginProcessingUrl("/login") 设置登录页的请求(Controller)
.successHandler(myAuthenticationSuccessHandler) 设置成功跳转
failureHandler(myAuthenctiationFailureHandler) 设置失败跳转
logout() 登出设置
invalidateHttpSession(true) 清空session
deleteCookies(“JESSIONID”) 删除cookies
logoutSuccessHandler(myLogoutSuccessHandler) 登出成功跳转

http.exceptionHandling().accessDeniedHandler(restAuthenticationAccessDeniedHandler):异常跳转

2.1 跳转处理Handler

上述代码中自定义了很多跳转处理的Handler,下面就对Handler进行编写

2.1.1 登录成功

/**
 * @Author: LySong
 * @Date: 2020/3/20 22:10
 */
@Component
@Slf4j
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    ObjectMapper objectMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        log.info("登陆成功");
        httpServletResponse.setHeader("Access-Control-Allow-Origin","*");
        httpServletResponse.setHeader("Access-Control-Allow-Methods","*");
        httpServletResponse.setContentType("application/json;charset=UTF8");
        httpServletResponse.setStatus(HttpStatus.OK.value());

        httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication));
    }
}

2.1.2 登录失败

@Component
@Slf4j
public class MyAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        log.info("登陆失败");

        if ("json".equals("json")) {
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Methods", "*");
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            Map<String,Object> map = new HashMap<>();
            if(exception instanceof LockedException){
                map.put("message","账户被锁定,登录失败!");
            }else if(exception instanceof BadCredentialsException){
                map.put("message","账户名或密码输入错误,登录失败!");
            }else if(exception instanceof DisabledException){
                map.put("message","账户被禁用,登录失败!");
            }else if(exception instanceof AccountExpiredException){
                map.put("message","账户已过期,登录失败!");
            }else if(exception instanceof CredentialsExpiredException){
                map.put("message","密码已过期,登录失败!");
            }else{
                map.put("message","登录失败!");
            }
            response.getWriter().write(objectMapper.writeValueAsString(map));
        }else{
            super.onAuthenticationFailure(request, response, exception);
        }
    }
}

2.1.3 登出成功

/**
 * @Author: LySong
 * @Date: 2020/3/20 23:53
 */
@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.sendRedirect("/login");
    }
}

2.1.4 未授权页面

/**
 * @Author: LySong
 * @Date: 2020/3/20 23:26
 */
@Component
public class RestAuthenticationAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setStatus(httpServletResponse.SC_FORBIDDEN);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType("application/json;charset=UTF8");
        httpServletResponse.sendRedirect("/403.html");
    }
}

3.Controller权限设置

在上述的过程中我们在LoginUser类(实现了UserDetial接口)中在Collection<? extends GrantedAuthority> getAuthorities() 向SpringSercurity设置了用户对应的权限列表,权限在数据库中的字段为: (空字段为菜单,不是按钮)

带你使用Spring Secutity实现权限管理(干货无套话)
那么我们这里将使用注解,对Controller进行配置,以实现对登入用户的权限管理,限制用户使用未被授权的功能

注解 作用
@PreAuthorize(“hasAuthority(‘sys:menu:del’)”) 控制方法是否能够被调用
hasAuthority(“ ”) 允许拥有对应权限的用户调用方法,否则返回403页面

到这里我们就可以完整的实现整个权限控制的流程了,完整的代码可以访问我的GitHub进行查看

三.点击进入GitHub查看完整代码

相关标签: Spring全家桶