1. 程式人生 > >springboot+security框架整合

springboot+security框架整合

springboot+security框架整合

 

springboot專案搭建大家可以去請教度娘,有很多文章,這裡主要講解springboot和security安全框架的整合,因為springmvc跟security整合中,大部分都是採用配置檔案的形式,本例中完全沒用配置檔案,配置檔案的方式寫起來比較省事,但是比較難懂,導致我在寫這個的時候網上搜的資料也不多,很難搞,好不容易弄好了,怕自己忘記,特此記錄一下。

表格部分:分為五個表格

sys_user 

這裡寫圖片描述

sys_role

這裡寫圖片描述

sys_menu

這裡寫圖片描述

sys_user_role

這裡寫圖片描述

sys_role_menu

這裡寫圖片描述

分表建立實體Bean,資料訪問層是用的hibernate,具體程式碼可參見附件

安全框架配置部分

配置檔案那種的方式最主要的是配置檔案,而不用配置檔案最主要的就是自定義的去實現WebSecurityConfigurerAdapter類,大體的思路為: 
1、WebSecurityConfig===》WebSecurityConfigurerAdapter(主要配置檔案) 
2、MyAuthenticationProvider==》AuthenticationProvider(自定義驗證使用者名稱密碼) 
3、CustomUserDetailsService==》UserDetailsService(MyAuthenticationProvider需要呼叫) 
4、mySecurityFilter==》AbstractSecurityInterceptor 、Filter(自定義的過濾器) 
5、FilterSourceMetadataSource==》FilterInvocationSecurityMetadataSource(過濾器呼叫,過濾器載入資源) 
6、MyAccessDecisionManager ==》AccessDecisionManager(過濾器呼叫,驗證使用者是否有許可權訪問資源)

下面是WebSecurityConfig

package com.zy.configuration;
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.authentication.AuthenticationProvider;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.stereotype.Service;

/**
 * @Author zhang
 * @create 2017-07-14-15:51
 * @desc ${DESCRIPTION}
 **/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)//開啟security註解
public class WebSecurityConfig  extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyAuthenticationProvider authenticationProvider;

    @Autowired
    private MySecurityFilter mySecurityFilter;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //允許所有使用者訪問"/"和"/home"
        http
                .addFilterBefore(mySecurityFilter, FilterSecurityInterceptor.class)//在正確的位置新增我們自定義的過濾器
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/", "/home","403").permitAll()//訪問:/home 無需登入認證許可權
                //其他地址的訪問均需驗證許可權
                .anyRequest().authenticated()//其他所有資源都需要認證,登陸後訪問
                .and()
                    .formLogin()
                    //指定登入頁是"/login"
                    .loginPage("/login")
                    .defaultSuccessUrl("/index")//登入成功後預設跳轉到"/hello"
//                    .failureUrl("/403")
                    .permitAll()
                    //.successHandler(loginSuccessHandler())//code3
                .and()
                    .logout()
                    .logoutSuccessUrl("/")//退出登入後的預設url是"/home"
                    .permitAll()
                .and()
                    .rememberMe()//登入後記住使用者,下次自動登入,資料庫中必須存在名為persistent_logins的表
                    .tokenValiditySeconds(1209600);  ;
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
        auth.userDetailsService(customUserDetailsService()).passwordEncoder(passwordEncoder());
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**");
        //可以仿照上面一句忽略靜態資源
    }

//    @Autowired
//    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//        auth.authenticationProvider(authenticationProvider);
//    }

    /**
     * 設定使用者密碼的加密方式為MD5加密
     * @return
     */
    @Bean
    public Md5PasswordEncoder passwordEncoder() {
        return new Md5PasswordEncoder();

    }

    /**
     * 自定義UserDetailsService,從資料庫中讀取使用者資訊
     * @return
     */
    @Bean
    public CustomUserDetailsService customUserDetailsService(){
        return new CustomUserDetailsService();
    }
//
}

類MyAuthenticationProvider 自定義的使用者名稱密碼驗證,呼叫了loadUserByUsername方法

@Component
public class MyAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException{
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();
        UserDetails user = customUserDetailsService.loadUserByUsername(username);
        if(user == null){
            throw new BadCredentialsException("使用者沒有找到");
        }

        //加密過程在這裡體現
        if (!password.equals(user.getPassword())) {
            System.out.print("密碼錯誤");
            throw new BadCredentialsException("密碼錯誤");
        }

        Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
        return new UsernamePasswordAuthenticationToken(user, password, authorities);
    }

    public boolean supports(Class<?> var1){
        return true;
    }
}

類CustomUserDetailsService 實現 UserDetailsService

@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Autowired
    private ActionRepository actionRepository;

    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException{
        try {
            UserBean user = userService.findUserByName(userName);
            if(null == user){
                throw new UsernameNotFoundException("UserName " + userName + " not found");
            }
            // SecurityUser實現UserDetails並將SUser的Email對映為username
            List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
            for (RoleBean ur : user.getRoleBeans()) {
                String name = ur.getRoleName();
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(name);
                authorities.add(grantedAuthority);
            }
            return new User(
                    user.getUsername(),
                    user.getPassword(),
                    authorities);
        }catch ( Exception e){
            e.printStackTrace();
            return null;
        }

    }

} 

類MySecurityFilter 自定義過濾器,攔截器我本來沒有自定義,但是會有問題一些訪問的資源什麼的沒有辦法過濾掉。

package com.zy.configuration;
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.authentication.AuthenticationManager;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.servlet.*;
import java.io.IOException;

/**
 * @Author toplion
 * @create 2017-08-03-17:00
 * @desc ${DESCRIPTION}
 **/
@Service
public class MySecurityFilter extends AbstractSecurityInterceptor implements Filter{
    @Autowired
    private FilterSourceMetadataSource filterInvocationSource;
    @Autowired
    private MyAccessDecisionManager myAccessDecisionManager;
    @Autowired
    private AuthenticationManager authenticationManager;
    @PostConstruct
    public void init(){
        super.setAuthenticationManager(authenticationManager);
        super.setAccessDecisionManager(myAccessDecisionManager);
    }
    public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException{
        FilterInvocation fi = new FilterInvocation( request, response, chain );
        invoke(fi);
    }
    public Class<? extends Object> getSecureObjectClass(){
        return FilterInvocation.class;
    }
    public void invoke( FilterInvocation fi ) throws IOException, ServletException{
        System.out.println("filter..........................");
        InterceptorStatusToken  token = super.beforeInvocation(fi);
        try{
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        }finally{
            super.afterInvocation(token, null);
        }
    }
    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource(){
        System.out.println("filtergergetghrthetyetyetyetyj");
        return this.filterInvocationSource;
    }
    public void destroy(){
        System.out.println("filter===========================end");
    }
    public void init( FilterConfig filterconfig ) throws ServletException{
        System.out.println("filter===========================");
    }
}

類FilterSourceMetadataSource

@Service
public class FilterSourceMetadataSource implements FilterInvocationSecurityMetadataSource {

    private static Map<String, Collection<ConfigAttribute>> resourceMap = new HashMap<String, Collection<ConfigAttribute>>();

    private ActionRepository menuService;

    private RequestMatcher pathMatcher;

    @Autowired
    public FilterSourceMetadataSource(ActionRepository repository) {
        this.menuService = repository;
        loadResourcePermission();
    }

    /**
     * 返回所請求資源所需要的許可權
     * 針對資源的URL
     * @param o
     * @return
     * @throws IllegalArgumentException
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        Iterator<String> resourceUrls = resourceMap.keySet().iterator();
        while (resourceUrls.hasNext()) {
            String resUrl = resourceUrls.next();
            System.out.print("**********************************"+resUrl);
            if(null == resUrl || resUrl.isEmpty()) {
                continue;
            }
            pathMatcher = new AntPathRequestMatcher(resUrl);
            if (pathMatcher.matches(((FilterInvocation) o).getRequest())) {
                Collection<ConfigAttribute> configAttributes = resourceMap.get(resUrl);
                return configAttributes;
            }
        }
        return null;
    }

    /**
     * 獲取所有配置許可權
     * @return
     */
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        Collection<Collection<ConfigAttribute>> cacs = resourceMap.values();
        Collection<ConfigAttribute> cac = new HashSet<ConfigAttribute>();
        for (Collection<ConfigAttribute> c: cacs) {
            cac.addAll(c);
        }
        return cac;
    }

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

    /**
     * 載入資源的許可權
     */
    private void loadResourcePermission() {
        loadMenuPermisson();
    }

    /**
     * 載入選單的許可權
     */
    private void loadMenuPermisson() {
        List<ActionBean> menus = menuService.findAll();
        for (ActionBean menu: menus) {
            List<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
//            MenuEntity currMenu = menuService.getMenuById(menu.getMenuId());
            Hibernate.initialize(menu);
            ConfigAttribute configAttribute = new SecurityConfig(menu.getMenuCode());
            configAttributes.add(configAttribute);
            if(null != resourceMap.get(menu.getMenuUrl())) {
                resourceMap.get(menu.getMenuUrl()).addAll(configAttributes);
            } else {
                resourceMap.put(menu.getMenuUrl(), configAttributes);
            }
        }
        System.out.println(resourceMap);

    }

}

類MyAccessDecisionManager

@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
    /**
     * 驗證使用者是否有許可權訪問資源
     * @param authentication
     * @param o
     * @param configAttributes
     * @throws AccessDeniedException
     * @throws InsufficientAuthenticationException
     */
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        /*允許訪問沒有設定許可權的資源*/
        if(configAttributes == null) {
            return;
        }
        /*一個資源可以由多個許可權訪問,使用者擁有其中一個許可權即可訪問該資源*/
        Iterator<ConfigAttribute> configIterator = configAttributes.iterator();
        while (configIterator.hasNext()) {
            ConfigAttribute configAttribute = configIterator.next();
            String needPermission = configAttribute.getAttribute();
            for(GrantedAuthority ga : authentication.getAuthorities()) {
                if(needPermission.equals(ga.getAuthority())) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("沒有許可權訪問");
    }

    /**
     * 特定configAttribute的支援
     * @param configAttribute
     * @return
     */
    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        /*支援所有configAttribute*/
        return true;
    }

    /**
     * 特定安全物件型別支援
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(Class<?> aClass) {
        /*支援所有物件型別*/
        return true;
    }
}