SpringBoot与SpringSecurity整合的初使用
1.背景:
最近的一个项目中,需要做web页面的管理后台,需要对不同角色进行不同的管理,特此研究了一下SpringSecurity的使用。
2.正题:
1.采用框架:springboot,springsecurity,jpa
2.采用idea的spring initializr 初始化一个springboot项目,勾选模块为web,securiy,和jpa,jdbc。
3.表结构:
总共有三张表,分别为user,user_authorities,authentication
user表
authentication表
user_authorities表:
4.代码开始:
4.1 首先,创建jpa对应的实体类:
这个就不贴代码了,根据上面的表结构创建对应的类,加上jpa对应的注解即可
4.2 创建Repository类,继承JpaRepository
4.3 创建SpringSecurity配置类WebSecurityConfigurerAdapter:
@EnableWebSecurity public class MySecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserDetailService userDetailsService; @Autowired private AuthenticationRepository authenticationRepository; @Override protected void configure(HttpSecurity http) throws Exception { //super.configure(http); http.csrf().disable(); //定义请求规则,查询数据库,将authentication表中的请求规则查询配置 List<Authentication> authentications = authenticationRepository.findAll(); http.authorizeRequests() .antMatchers("/").permitAll(); for(Authentication authentication :authentications){ http.authorizeRequests(). antMatchers(authentication.getDescription()) .hasRole(authentication.getName(). substring(authentication.getName().indexOf("ROLE_")+5)); } //开启自动配置的登录功能 http.formLogin(); //1./login请求来到登陆页面 //2.重定向到/login?error 表示登录失败 //3. //开启自动注销功能 //访问logout表示注销,清空session //注销成功会默认返回/login?logout页面 //可以通过配置logoutSuccessUrl来配置注销成功的返回地址 http.logout().logoutSuccessUrl("/"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authenticationProvider()); } public DaoAuthenticationProvider authenticationProvider(){ DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder(8)); authenticationProvider.setUserDetailsService(userDetailsService); return authenticationProvider; } }
4.4 配置 AccessDecisionManager类
@Service public class MyAccessDecisionManager implements AccessDecisionManager { public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if (configAttributes == null ) { throw new AccessDeniedException("对不起,您没有此权限"); } SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(sdf.format(new Date())+":\t"+object.toString()); System.out.println("configAttributes=="+configAttributes); System.out.println("authentication=="+authentication.getAuthorities()); for(ConfigAttribute ca:configAttributes){ String needRole = ca.getAttribute(); for(GrantedAuthority userGA:authentication.getAuthorities()) { if(needRole.equals(userGA.getAuthority())) { // ga is user's role. return ; } } } throw new AccessDeniedException("对不起,您没有此权限"); } public boolean supports(ConfigAttribute arg0) { // TODO Auto-generated method stub return true; } public boolean supports(Class<?> arg0) { // TODO Auto-generated method stub return true; } }
4.5 实现一个继承userDetailService的类,根据业务逻辑实现loadUserByUserName
@Service public class MyUserDetailService implements UserDetailsService { @Autowired private UsersRepository userRepository; @Autowired private UserAuthoritiesRepository userAuthoritiesRepository; @Override @Transactional public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { User user = userRepository.findByUsername(s); if(user == null){ throw new UsernameNotFoundException(s); } List<GrantedAuthority> authorities = new ArrayList<>(); List<UserAuthorities> userAuthorities = userAuthoritiesRepository.findAllByUser(user); for(UserAuthorities userAuthorities1:userAuthorities){ authorities.add(new SimpleGrantedAuthority(userAuthorities1.getAuthentication().getName())); } return new MyUserPrincipal(user,authorities); } }
4.6 实现AbstractSecurityInterceptor,继承Filter
@Service public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { @Autowired private FilterInvocationSecurityMetadataSource securityMetadataSource; @Autowired public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) { super.setAccessDecisionManager(myAccessDecisionManager); } public FilterInvocationSecurityMetadataSource getSecurityMetadataSource(){ return this.securityMetadataSource; } @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 Class<?> getSecureObjectClass() { return FilterInvocation.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } }
4.7 实现FilterInvocationSecurityMetadataSource
@Service public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource { private HashMap<String, Collection<ConfigAttribute>> map = null; @Autowired private AuthenticationRepository authenticationRepository; /** * 加载权限表中所有权限,这里不想从数据库中获取直接写在了这 */ public void loadResourceDefine() { map = new HashMap<>(); Collection<ConfigAttribute> array; // ConfigAttribute cfg, cfg1,cfg2; array = new ArrayList<>(); List<Authentication> authentications = authenticationRepository.findAll(); for(Authentication authentication:authentications){ System.out.println(authentication); ConfigAttribute cfg = new SecurityConfig(authentication.getName()); array.add(cfg); map.put(authentication.getDescription(),array); } } //此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法, // 用来判定用户是否有此权限。如果不在权限表中则放行。 @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { System.out.println("object的类型为:" + object.getClass()); FilterInvocation filterInvocation = (FilterInvocation) object; String url = filterInvocation.getRequestUrl(); System.out.println("访问的URL地址为(包括参数):" + url); url = filterInvocation.getRequest().getServletPath(); System.out.println("访问的URL地址为:" + url); if (map == null) loadResourceDefine(); //object 中包含用户请求的request 信息 final HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); AntPathRequestMatcher matcher; String resUrl; for (Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) { resUrl = iter.next(); matcher = new AntPathRequestMatcher(resUrl); //matches() 方法用于检测字符串是否匹配给定的正则表达式 boolean a = matcher.matches(request); if (matcher.matches(request)) { Collection<ConfigAttribute> c = map.get(resUrl); return map.get(resUrl); } } return null; // return collection; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { //UsernamePasswordAuthenticationToken.class.equals(clazz); return FilterInvocation.class.isAssignableFrom(clazz); //return true; } }