天天看點

五分鐘帶你玩轉SpringSecurity(四)配置全解析,帶你掌握security核心要點

1認證

認證:辨識使用者是否本系統使用者。

優勢:1 提供多樣式的加密方法

           2 提供多樣式的使用者存儲方式

           3 使用者無需關注驗證封裝業務 隻需要提供查詢方法即可

           4 多樣式的認證方式

           5 提供使用者資訊擷取方式

可擴充的功能

      1 記住我

      2 郵箱驗證

      3 手機驗證

      4 驗證碼驗證

配置詳解

spring security統一實作WebSecurityConfigurerAdapter接口 按照以下需求添加以下配置 就可以整合成功

@Configuration
@EnableWebSecurity // 開啟springsecurity過濾鍊 filter
@EnableGlobalMethodSecurity(prePostEnabled = true) // 開啟注解方法級别權限控制
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
 
    //實作輸入是明文 存儲到資料庫為密文 寫死即可
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 明文+随機鹽值》加密存儲
        return new BCryptPasswordEncoder();
    }
 
    //使用者驗證的業務流程 也就是查詢使用者的業務代碼
    @Autowired
    UserDetailsService customUserDetailsService;
 
    /**
     * 認證管理器: 将上文查詢使用者是否存在的service按樣式寫入 修改service 其餘寫死 還有密碼寫死或存放記憶體中等方式 這裡不讨論
     * 1. 認證資訊(使用者名,密碼)
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 資料庫存儲的密碼必須是加密後的也就是必須要有passwordEncoder,不然會報錯:There is no PasswordEncoder mapped for
        auth.userDetailsService(customUserDetailsService);
    }
 
    //驗證碼配置
    @Autowired
    private ImageCodeValidateFilter imageCodeValidateFilter;
 
    //當驗證成功後可以傳回json或者路徑 但是現在基于前背景分離 大多數都是傳回json AuthenticationSuccessHandler為成功後轉為json的處理 按照本文配置即可
    @Autowired
    private AuthenticationSuccessHandler customAuthenticationSuccessHandler;
 
    //當驗證成功後可以傳回json或者路徑 但是現在基于前背景分離 大多數都是傳回json AuthenticationFailureHandler為失敗後轉為json的處理 按照本文配置即可
    @Autowired
    private AuthenticationFailureHandler customAuthenticationFailureHandler;
 
    //建立資料源
    @Autowired
    DataSource dataSource;
 
 
    @Autowired
    private InvalidSessionStrategy invalidSessionStrategy;
 
    /**
     * 當同個使用者session數量超過指定值之後 ,會調用這個實作類
     */
    @Autowired
    private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;
 
    /**
     * 持久化token
     * @return
     */
    @Bean
    public JdbcTokenRepositoryImpl jdbcTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        // 是否啟動項目時自動建立表,true自動建立
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
    /**
     * 核心攔截器 當你認證成功之後 ,springsecurity它會重寫向到你上一次請求上
     * 資源權限配置:
     * 1. 被攔截的資源
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //調用驗證碼過濾器 下文會詳細介紹
        http.addFilterBefore(imageCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin() // 表單登入方式
                .loginPage("/login/page") //登入頁的頁面位址
                .loginProcessingUrl("/login/form") // 登入表單送出處理url, 預設是/login
                .usernameParameter("username") //預設的是 username
                .passwordParameter("password")  // 預設的是 password
                .successHandler(customAuthenticationSuccessHandler) //登入成功傳回的json
                .failureHandler(customAuthenticationFailureHandler) //登入失敗傳回的json
                .and() //每個類型的配置 以.and()間隔 相當于;
                .authorizeRequests() // 授權請求
                .antMatchers("/login/page",
                        "/code/image","/mobile/page", "/code/mobile",
                        "/code/image",
                        "/code/mobile",
                        "/mobile/page"
                ).permitAll() // 放行/login/page不需要認證可通路 因為如果在調用驗證接口時還需要權限 那麼就沒有入口了 是以一些不需要登入就能通路的接口在此配置
 
                // 此處是鑒權
                // 有 sys:user 權限的可以通路任意請求方式的/role
                .antMatchers("/user").hasAuthority("sys:user")
                // 有 sys:role 權限的可以通路 get方式的/role
                .antMatchers(HttpMethod.GET,"/role").hasAuthority("sys:role")
                .antMatchers(HttpMethod.GET, "/permission")
                // ADMIN 注意角色會在前面加上字首 ROLE_ , 也就是完整的是 ROLE_ADMIN, ROLE_ROOT
                .access("hasAuthority('sys:premission') or hasAnyRole('ADMIN', 'ROOT')")
                // 此處是鑒權
 
                .anyRequest().authenticated() //所有通路該應用的http請求都要通過身份認證才可以通路
                .and()
                .rememberMe() //記住我功能
                 //記住功能配置
                .tokenRepository(jdbcTokenRepository()) //儲存token資訊
                .tokenValiditySeconds(604800) //記住我有效時長
                .and()
                .sessionManagement()// session管理
                .invalidSessionStrategy(invalidSessionStrategy) //當session失效後的處理類 //.expiredSessionStrategy(sessionInformationExpiredStrategy)// 當使用者達到最大session數後,則調用此處的實作
                .maximumSessions(1) // 每個使用者在系統中最多可以有多少個session
                .maxSessionsPreventsLogin(true) // 當一個使用者達到最大session數,則不允許後面再登入
                .sessionRegistry(sessionRegistry())
                .and().and()
                .logout()//登出相關
                .addLogoutHandler(customLogoutHandler) // 退出清除緩存
                .logoutUrl("/user/logout") // 退出請求路徑
                .logoutSuccessUrl("/mobile/page") //退出成功後跳轉位址
                .deleteCookies("JSESSIONID") // 退出後删除什麼cookie值
        ;// 注意不要少了分号
        http.csrf().disable(); // 關閉跨站請求僞造
    }
 
    /**
     * 退出清除緩存
     */
    @Autowired
    private CustomLogoutHandler customLogoutHandler;
 
    /**
     * 為了解決退出重新登入問題
     * @return
     */
    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }
    /**
     * 一般是針對靜态資源放行
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web){
        web.ignoring().antMatchers("/js/**", "/css/**");
    }
}      

清除緩存方法

@Component
public class CustomLogoutHandler implements LogoutHandler {
 
    @Autowired
    private SessionRegistry sessionRegistry;
 
    @Override
    public void logout(HttpServletRequest request,
                       HttpServletResponse response,
                       Authentication authentication) {
        // 退出之後 ,将對應session從緩存中清除 SessionRegistryImpl.principals
        sessionRegistry.removeSessionInformation(request.getSession().getId());
    }
}      

編寫user驗證方法類

需要繼承UserDetailsService  為jar包提供

1. public interface UserDetailsService {
2.     UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
3. }      

擷取SysUser實體方法和擷取此人菜單的方法 這裡不多說

public abstract class AbstractUserDetailsService implements UserDetailsService {
 
    @Autowired
    private SysPermissionService sysPermissionService;
 
    /**
     * 這個方法交給子類去實作它,查詢使用者資訊
     *
     * @param usernameOrMobile 使用者名或者手機号
     * @return
     */
    public abstract SysUser findSysUser(String usernameOrMobile);
 
    @Override
    public UserDetails loadUserByUsername(String usernameOrMobile) throws UsernameNotFoundException {
        // 1. 通過請求的使用者名去資料庫中查詢使用者資訊
        SysUser sysUser = findSysUser(usernameOrMobile);
        // 通過使用者id去擷取權限資訊
        findSysPermission(sysUser);
        return sysUser;
    }
 
    private void findSysPermission(SysUser sysUser) {
        if (sysUser == null) {
            throw new UsernameNotFoundException("使用者名或密碼錯誤");
        }
        // 2. 查詢該使用者有哪一些權限
        List<SysPermission> permissions = sysPermissionService.findByUserId(sysUser.getId());
        if (CollectionUtils.isEmpty(permissions)) {
            return;
        }
        // 在左側菜單 動态渲染會使用,目前先把它都傳入
        sysUser.setPermissions(permissions);
        // 3. 封裝權限資訊
        List<GrantedAuthority> authorities = Lists.newArrayList();
        for (SysPermission sp : permissions) {
            // 權限辨別
            String code = sp.getCode();
            authorities.add(new SimpleGrantedAuthority(code));
        }
        sysUser.setAuthorities(authorities);
    }
}      
@Component("customUserDetailsService")
public class CustomUserDetailsService extends AbstractUserDetailsService{
    Logger logger = LoggerFactory.getLogger(getClass());
 
    @Autowired // 不能删掉,不然報錯
    PasswordEncoder passwordEncoder;
 
    @Autowired
    SysUserService sysUserService;
 
    @Override
    public SysUser findSysUser(String usernameOrMobile) {
        logger.info("請求認證的使用者名: " + usernameOrMobile);
        // 1. 通過請求的使用者名去資料庫中查詢使用者資訊
        return sysUserService.findByUsername(usernameOrMobile);
    }
}      
CustomAuthenticationSuccessHandler與CustomAuthenticationFailureHandler      
//此類沒有注入,因為樓主并沒有使用
@Component("customAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
 
    @Autowired(required = false) // 容器中可以不需要有接口的實作,如果有則自動注入
    AuthenticationSuccessListener authenticationSuccessListener;
 
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
        HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        if(authenticationSuccessListener != null) {
            // 當認證之後 ,調用此監聽,進行後續處理,比如加載使用者權限菜單
            authenticationSuccessListener.successListener(request, response, authentication);
        }
        if(LoginResponseType.JSON.equals(
                    "post")) {
            // 認證成功後,響應JSON字元串
            MengxueguResult result = MengxueguResult.ok("認證成功");
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(result.toJsonString());
        }else {
            //重定向到上次請求的位址上,引發跳轉到認證頁面的位址
            logger.info("authentication: " + JSON.toJSONString(authentication));
            super.onAuthenticationSuccess(request, response, authentication);
        }
 
    }
}      
@Component("customAuthenticationFailureHandler")
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
 
    /**
     *
     * @param exception 認證失敗時抛出異常
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
            HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        if(LoginResponseType.JSON.equals("post")) {
            // 認證失敗響應JSON字元串,
            MengxueguResult result = MengxueguResult.build(HttpStatus.UNAUTHORIZED.value(), exception.getMessage());
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(result.toJsonString());
        }else {
            // 重寫向回認證頁面,注意加上 ?error
//            super.setDefaultFailureUrl(securityProperties.getAuthentication().getLoginPage()+"?error");
            // 擷取上一次請求路徑
            String referer = request.getHeader("Referer");
            logger.info("referer:" + referer);
 
            // 如果下面有值,則認為是多端登入,直接傳回一個登入位址
            Object toAuthentication = request.getAttribute("toAuthentication");
            String lastUrl = toAuthentication != null ? "/login/page"
                    : StringUtils.substringBefore(referer,"?");
            logger.info("上一次請求的路徑 :" + lastUrl);
            super.setDefaultFailureUrl(lastUrl+"?error");
            super.onAuthenticationFailure(request, response, exception);
        }
    }
}