SpringBoot整合Spring Secruity的基本用法和数据库动态权限配置
一个Java菜鸟的修炼记录 个人博客 youngljx.top
SpringBoot 安全管理之 Spring Security
基于SpringBoot的自动化配置安全管理使用Spring Security比Shiro更适用
基本配置
1.基本用法,引入依赖,项目中的所有资源会默认的被保护起来
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.简单地配置用户名和密码,不会在在生成随机的密码,示例
spring.security.user.name=ljx
spring.security.user.password=123
spring.security.user.roles=admin
3.基于内存的认证,简单示例
@Bean
PasswordEncoder passwordEncoder() {
return new NoOpPasswordEncoder().getInstance;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("ljx")
.password("123")
.roles("admin")
.and()
.withUser("dage")
.password("123")
.roles("user");
}
登录表单,注销登录,密码加密的详细配置见下文
基于数据库的认证,动态权限配置
1.数据库表模型如下:
值得注意的是,数据库表role的字段前要加ROLE_,如果不加如下修改:
用户实体类实现UserDetails接口,重写其中的方法
@Data
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
private Boolean enabled;
private Boolean locked;
private List<Role> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
//"ROLE_"+role.getName()
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !locked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public String getPassword() {
return password;
}
}
定义UserService实现UserServiceDetails
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("账户不存在!");
}
user.setRoles(userMapper.getUserRolesByUid(user.getId()));
return user;
}
}
2.自定义 FilterInvocationSecurityMetadataSource确定一个请求需要哪些角色,代码如下:
@Component
public class MyFilter implements FilterInvocationSecurityMetadataSource {
/**
* @Description: 定义路径匹配符
*/
AntPathMatcher pathMatcher=new AntPathMatcher();
@Autowired
MenuService menuService;
/**
* @Description: 动态匹配请求url的角色权限
* @Param: [o]
* @return: java.util.Collection<org.springframework.security.access.ConfigAttribute>
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
//获取请求url地址
String requestUrl = ((FilterInvocation) o).getRequestUrl();
//获取menu中的设置的pattern以及角色role的集合
List<Menu> allMenus = menuService.getAllMenus();
//遍历比较pattern和url
for (Menu menu : allMenus) {
if (pathMatcher.match(menu.getPattern(),requestUrl)){
List<Role> roles = menu.getRoles();
String[] rolesStr=new String[roles.size()];
for (int i = 0; i < rolesStr.length; i++) {
rolesStr[i]=roles.get(i).getName();
}
return SecurityConfig.createList(rolesStr);
}
}
//路径都不匹配,设置默认的返回值
return SecurityConfig.createList("ROLE_login");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableForm(clazz);
}
}
3.自定义AccessDecisionManager进行角色对比,代码如下:
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection)
throws AccessDeniedException, InsufficientAuthenticationException {
for (ConfigAttribute attribute : collection) {
if ("ROLE_login".equals(attribute.getAttribute())){
if (authentication instanceof AnonymousAuthenticationToken) {
throw new AccessDeniedException("权限不足!");
}else {
return;
}
}
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(attribute.getAttribute())){
return;
}
}
}
throw new AccessDeniedException("权限不足!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
涉及到的MenuMapper.xml的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ljx.securitydb.mapper.MenuMapper">
<!--resultMap将statement sql查询结果映射为java对象-->
<resultMap type="com.ljx.securitydb.bean.Menu" id="MenuMap">
<result property="id" column="id" />
<result property="pattern" column="pattern" />
<collection property="roles" ofType="com.ljx.securitydb.bean.Role">
<id property="id" column="rid"/>
<result property="name" column="rname"/>
<result property="nameZh" column="rnameZh"/>
</collection>
</resultMap>
<!--多表关联查询-->
<select id="getAllMenus" resultMap="MenuMap">
select m.*,r.id as rid,r.name as rname,r.nameZh as rnameZh from menu m
left join menu_rloe mr on m.id=mr.mid
left join role r on mr.rid=r.id
</select>
</mapper>
统一结果响应类如下:
public class RespBean {
private Integer status;
private String msg;
private Object object;
public static RespBean ok(String msg){
return new RespBean(200,msg,null);
}
public static RespBean ok(String msg,Object object){
return new RespBean(200,msg,object);
}
public static RespBean error(String msg){
return new RespBean(500,msg,null);
}
public static RespBean error(String msg,Object object){
return new RespBean(500,msg,object);
}
private RespBean(){}
private RespBean(Integer status, String msg, Object object) {
this.status = status;
this.msg = msg;
this.object = object;
}
//省略getter和setter
}
4.SecruityConfig 的配置如下:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Autowired
MyFilter myFilter;
@Autowired
MyAccessDecisionManager myAccessDecisionManager;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@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(myFilter);
o.setAccessDecisionManager(myAccessDecisionManager);
return o;
}
})
.and()
//登录表单配置
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/doLogin")
.loginPage("/login")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
//获取登录成功的用户对象并返回统一响应对象
Hr hr = (Hr) authentication.getPrincipal();
hr.setPassword(null);
RespBean ok = RespBean.ok("登录成功", hr);
String s = new ObjectMapper().writeValueAsString(ok);
out.write(s);
out.flush();
out.close();
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setContentType("application/json ; charset=utf-8");
PrintWriter out = response.getWriter();
RespBean respBean = RespBean.error("登录失败");
if (e instanceof LockedException){
respBean.setMsg("账户被锁定,请联系管理员!");
}else if (e instanceof CredentialsExpiredException){
respBean.setMsg("密码过期,请联系管理员!");
}else if (e instanceof AccountExpiredException) {
respBean.setMsg("账户过期,请联系管理员!");
}else if (e instanceof DisabledException){
respBean.setMsg("账户被禁用,请联系管理员!");
}else if (e instanceof BadCredentialsException){
respBean.setMsg("账户名或密码错误,请重新输入!");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
})
.permitAll()
.and()
//注销登录配置
.logout()
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(new ObjectMapper().writeValueAsString(RespBean.ok("注销成功!")));
out.flush();
out.close();
}
})
.and()
//测试关闭crsf
.csrf()
.disable();
}
}
基于上面的配置已经实现了动态权限配置,权限和资源的关系就可以在menu_role表中动态调整
记录学习中的知识点,方便加深理解
未完待续,OAuth2

浙公网安备 33010602011771号