(四)spring boot 整合 spring security简单的登录和用户角色权限控制

首先添加spring security所用到的依赖:

<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
</dependency>

开始配置 spring security
1、 配置权限
通过继承WebSecurityConfigurerAdapter,我们可以在这里配置security的功能,例如加决策器,拦截器之类的等等。这里注意跳转的登录页面中表单所提交的用户名和密码所使用的字段是username和password,表单提交的url为/user/login,method为post。

package per.san.common.config;

import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import per.san.common.security.service.CustomUserDetailsService;
import per.san.sys.domain.SysPermission;
import per.san.sys.mapper.SysPermissionMapper;

import java.util.List;

/**
 * description:
 *
 * @author shencai.huang@hand-china.com
 * @date 12/29/2018 17:14
 * lastUpdateBy: shencai.huang@hand-china.com
 * lastUpdateDate: 12/29/2018
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //启用方法级的权限认证
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private SysPermissionMapper sysPermissionMapper;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        AuthenticationManager manager = super.authenticationManagerBean();
        return manager;
    }

    @Bean
    UserDetailsService customUserService() { //注册UserDetailsService 的bean
        return new CustomUserDetailsService();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserService()); //user Details Service验证

    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        List<SysPermission> permissionList = sysPermissionMapper.selectAllPublicPermission();
        List<String> urls = Lists.newArrayList("/public/**", "/login", "/register", "/find", "/swagger-ui.html",
                "/swagger-resources/**", "/images/**", "/webjars/**", "/v2/api-docs", "/configuration/ui",
                "/configuration/security");
        permissionList.forEach(item -> urls.add(item.getPath()));
        String[] antMatchers = new String[urls.size()];
        for (int i = 0; i < antMatchers.length; i++) {
            antMatchers[i] = urls.get(i);
        }
        http.authorizeRequests()        // 定义哪些URL需要被保护、哪些不需要被保护
                .antMatchers(antMatchers)
                .permitAll()         // 设置所有人都可以访问
//                .anyRequest()               // 任何请求,登录后可以访问
//                .permitAll()
                .and()
                // 设置登陆页
                .formLogin().loginPage("/login")
                .loginProcessingUrl("/user/login")
                // 设置登陆成功页
                .defaultSuccessUrl("/").permitAll()
                .and()
                .logout().permitAll()
                .and()
                .csrf().disable();          // 关闭csrf防护
    }

}

2、自定义CustomUserDetailsService继承UserDetailService
UserDetailService是spring security提供给我们的获取用户信息的Service,主要给security提供验证用户的信息,这里我们就可以自定义自己的需求了,我这个就是根据username从数据库获取该用户的信息,然后交给security进行后续比对和处理。

package per.san.common.security.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.stereotype.Component;
import per.san.sys.domain.SysPermission;
import per.san.sys.domain.SysUser;
import per.san.sys.mapper.SysPermissionMapper;
import per.san.sys.mapper.SysUserMapper;

import java.util.ArrayList;
import java.util.List;

/**
 * description:
 *
 * @author Sanchar
 * @date 2/16/2019 17:15
 * lastUpdateBy: Sanchar
 * lastUpdateDate: 2/16/2019 17:15
 */
@Component
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private SysPermissionMapper sysPermissionMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUser = sysUserMapper.selectByUsername(username);
        if (sysUser != null) {
            sysUser = sysUserMapper.selectByPrimaryKey(sysUser.getId());
            List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
            List<SysPermission> permissionList;
            if (sysUser.getIsAdmin()) {
                permissionList = sysPermissionMapper.selectAllProtectPermission();
                permissionList.forEach(item -> grantedAuthorities.add(new SimpleGrantedAuthority(item.getCode())));
            } else {
                permissionList = sysPermissionMapper.selectPermissionByUserId(sysUser.getId());
                permissionList.forEach(item -> grantedAuthorities.add(new SimpleGrantedAuthority(item.getCode())));
            }
            return new User(sysUser.getUserName(), sysUser.getPassword(), grantedAuthorities);
        } else {
            throw new UsernameNotFoundException("user: " + username + " do not exist!");
        }
    }
}

3、 新增自定义CustomAccessDecisionManager

package per.san.common.security.service;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Iterator;

/**
 * description:
 *
 * @author Sanchar
 * @date 2/19/2019 20:51
 * lastUpdateBy: Sanchar
 * lastUpdateDate: 2/19/2019 20:51
 */
@Service
public class CustomAccessDecisionManager implements AccessDecisionManager {

    // decide 方法是判定是否拥有权限的决策方法,
    //authentication 是释CustomUserDetailsService中循环添加到 GrantedAuthority 对象中的权限信息集合.
    //object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
    //configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        if (null == configAttributes || configAttributes.size() <= 0) {
            return;
        }
        ConfigAttribute c;
        String needRole;
        for (Iterator<ConfigAttribute> iterator = configAttributes.iterator(); iterator.hasNext(); ) {
            c = iterator.next();
            needRole = c.getAttribute();
            for (GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合
                if (needRole.trim().equals(ga.getAuthority())) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("no right");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

4、新增自定义CustomFilterSecurityInterceptor

package per.san.common.security.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

/**
 * description:
 *
 * @author Sanchar
 * @date 2/19/2019 20:54
 * lastUpdateBy: Sanchar
 * lastUpdateDate: 2/19/2019 20:54
 */
@Service
public class CustomFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {


    @Autowired
    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    @Autowired
    public void setMyAccessDecisionManager(CustomAccessDecisionManager customAccessDecisionManager) {
        super.setAccessDecisionManager(customAccessDecisionManager);
    }


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }


    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        //fi里面有一个被拦截的url
        //里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
        //再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            //执行下一个拦截器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }

    @Override
    public void destroy() {

    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }
}

5、新增自定义CustomInvocationSecurityMetadataSourceService

package per.san.common.security.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;
import per.san.sys.domain.SysPermission;
import per.san.sys.mapper.SysPermissionMapper;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

/**
 * description:
 *
 * @author Sanchar
 * @date 2/19/2019 20:56
 * lastUpdateBy: Sanchar
 * lastUpdateDate: 2/19/2019 20:56
 */
@Service
public class CustomInvocationSecurityMetadataSourceService  implements
        FilterInvocationSecurityMetadataSource {

    @Autowired
    private SysPermissionMapper sysPermissionMapper;

    private HashMap<String, Collection<ConfigAttribute>> map = null;

    private HashMap<String, SysPermission> permissionHashMap = null;

    /**
     * 加载权限表中所有权限
     */
    public void loadResourceDefine(){
        map = new HashMap<>();
        permissionHashMap = new HashMap<>();
        Collection<ConfigAttribute> array;
        ConfigAttribute cfg;
        List<SysPermission> permissionList = sysPermissionMapper.selectAllProtectPermission();
        for(SysPermission permission : permissionList) {
            array = new ArrayList<>();
            cfg = new SecurityConfig(permission.getCode());
            //此处只添加了用户的名字,其实还可以添加更多权限的信息,例如请求方法到ConfigAttribute的集合中去。此处添加的信息将会作为MyAccessDecisionManager类的decide的第三个参数。
            array.add(cfg);
            //用权限的getUrl() 作为map的key,用ConfigAttribute的集合作为 value,
            map.put(permission.getCode(), array);
            permissionHashMap.put(permission.getCode(), permission);
        }

    }

    //此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        if(map == null || permissionHashMap == null) loadResourceDefine();
        //object 中包含用户请求的request 信息
        HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
        AntPathRequestMatcher matcher;
        String code;
        for(Iterator<String> iterator = map.keySet().iterator(); iterator.hasNext(); ) {
            code = iterator.next();
            SysPermission permission = permissionHashMap.get(code);
            matcher = new AntPathRequestMatcher(permission.getPath(), permission.getMethod().toUpperCase());
            if(matcher.matches(request)) {
                return map.get(code);
            }
        }
        return null;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

源码已更新到githup:你个大猪蹄子.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值