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;
}
}