抜粋:
https://www.cnblogs.com/hhhshct/p/9726378.html
https://blog.csdn.net/weixin_42849689/article/details/89957823
https://blog.csdn.net/zhaoxichen_10/article/details/88713799
http://www.imooc.com/article/287214
A、春のセキュリティプロファイル
春のセキュリティは、エンタープライズアプリケーションのためのセキュアなアクセス制御宣言型セキュリティフレームワークSpringベースのソリューションを提供することができます。それはセット春豆のアプリケーションコンテキストで構成することができる提供し、フルに活用春のIoC、DI(制御の制御反転、DIの反転:依存性注入依存性注入)とAOP(アスペクト指向プログラミング)機能は、アプリケーションを提供します宣言セキュリティアクセス制御機能、作業のコードの重複をたくさん書いて、企業システムのセキュリティ制御を減らします。これは、Springベースのアプリケーションのための認証と承認のサポートを確実に軽量のセキュリティフレームワークです。Spring MVCの、それがうまく統合され、束ね人気のセキュリティ・アルゴリズムを搭載しています。セキュリティは、二つの操作(時々、アクセス制御と呼ばれる)「認証」と「検証」で構成されています。その役割の「認定」は、ユーザの宣言のためのプロセスを確立することである、この役割は、ユーザー、デバイス、またはシステムとすることができます。「検証」アプリケーション内でアクションを実行するユーザーを指します。許可された裁判官に到達する前に、役割は、認証プロセスで確立されています。
ユーザーのログイン、コールの実装AuthenticationManager、AuthenticationProcessingFilterブロックされますが、この情報がデータベース上にあってもよいので、AuthenticationManagerを使用すると、LDAPサーバ上に置くことができ、ユーザ認証情報(別の異なるサービスプロバイダのコールを取得するためにproviderManagerのを呼び出して、それが可能細かいXML設定ファイル)、ユーザーの検証に使用権限情報は、後でアクセスリソースの準備ができて、グローバル・キャッシュ内のユーザーSecurityContextHolderバネにカプセル化される場合。
リソースへのアクセス(すなわち、権限管理)、URLにアクセスするとき、すべての必要な許可を取得するためにFilterInvocationSecurityMetadataSourceの道を呼び出す、インターセプトにAbstractSecurityInterceptorインターセプターを通過しますが、承認マネージャAccessDecisionManagerを呼び出し、URLを傍受し、承認マネージャは、この春に渡します、:グローバルキャッシュSecurityContextHolderは、ユーザーの許可についての情報を得る、それが傍受されますと、フルアクセスがしたいURL URLをブロックして、配布ポリシー(など一票の決定、負の投票、多数決、そこ)に従いました十分な権限が返された場合、アクセスが十分でないと、エラーが不十分なアクセス許可のページにある呼び出します。
第二に、春のセキュリティの実装プロセス
三、春のセキュリティコードの実装
Spring Security的核心配置类是 WebSecurityConfig,抽象类
这是权限管理启动的入口,这里我们自定义一个实现类去它。然后编写我们需要处理的控制逻辑。
下面是代码,里面写的注释也比较详细。在里面还依赖了几个自定义的类,都是必须配置的。分别是
userService,
myFilterInvocationSecurityMetadataSource,
myAccessDecisionManager,
authenticationAccessDeniedHandler
3.1 WebSecurityConfig
package com.example.demo.config; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.config.annotation.ObjectPostProcessor; 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.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import com.example.demo.service.UserService; /** * spring-security权限管理的核心配置 * @author wjqhuaxia * */ @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) //全局 public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserService userService;//实现了UserDetailsService接口 @Autowired private MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;//权限过滤器(当前url所需要的访问权限) @Autowired private MyAccessDecisionManager myAccessDecisionManager;//权限决策器 @Autowired private AuthenticationAccessDeniedHandler authenticationAccessDeniedHandler;//自定义错误(403)返回数据 /** * 自定义的加密算法 * @return */ @Bean public PasswordEncoder myPasswordEncoder() { return new MyPasswordEncoder(); } /** * 配置userDetails的数据源,密码加密格式 */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService).passwordEncoder(myPasswordEncoder()); } /** * 配置放行的资源 */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring() .antMatchers("/index.html", "/static/**","/loginPage","/register") // 给 swagger 放行;不需要权限能访问的资源 .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/images/**", "/webjars/**", "/v2/api-docs", "/configuration/ui", "/configuration/security"); } /** * 这段配置,我认为就是配置Security的认证策略, 每个模块配置使用and结尾。 authorizeRequests()配置路径拦截,表明路径访问所对应的权限,角色,认证信息。 formLogin()对应表单认证相关的配置 logout()对应了注销相关的配置 httpBasic()可以配置basic登录 */ /** * HttpSecurity包含了原数据(主要是url) * 1.通过withObjectPostProcessor将MyFilterInvocationSecurityMetadataSource和MyAccessDecisionManager注入进来 * 2.此url先被MyFilterInvocationSecurityMetadataSource处理,然后 丢给 MyAccessDecisionManager处理 * 3.如果不匹配,返回 MyAccessDeniedHandler */ @Override protected void configure(HttpSecurity http) throws Exception { // authorizeRequests()配置路径拦截,表明路径访问所对应的权限,角色,认证信息 http.authorizeRequests() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O o) { o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource); o.setAccessDecisionManager(myAccessDecisionManager); return o; } }) .and() // formLogin()对应表单认证相关的配置 .formLogin() .loginPage("/loginPage") .loginProcessingUrl("/login") .usernameParameter("username") .passwordParameter("password") .permitAll() .failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); StringBuffer sb = new StringBuffer(); sb.append("{\"status\":\"error\",\"msg\":\""); if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) { sb.append("用户名或密码输入错误,登录失败!"); } else { sb.append("登录失败!"); } sb.append("\"}"); out.write(sb.toString()); out.flush(); out.close(); } }).successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); String s = "{\"status\":\"success\",\"msg\":\"登陆成功\"}"; out.write(s); out.flush(); out.close(); } }).and() // logout()对应了注销相关的配置 .logout() .permitAll() .and() .csrf() .disable() .exceptionHandling() .accessDeniedHandler(authenticationAccessDeniedHandler); } }
3.2 UserService
UserServiceImpl实现了UserDetailsService接口中的loadUserByUsername方法,方法执行成功后返回UserDetails对象,为构建Authentication对象提供必须的信息。UserDetails中包含了用户名,密码,角色等信息。
package com.example.demo.service.impl; import java.util.ArrayList; import java.util.List; 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.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.demo.dao.PermissionMapper; import com.example.demo.dao.RoleMapper; import com.example.demo.dao.UserMapper; import com.example.demo.model.Permission; import com.example.demo.model.User; import com.example.demo.service.UserService; /** * 实现了UserDetailsService接口中的loadUserByUsername方法 * 执行登录,构建Authentication对象必须的信息, * 如果用户不存在,则抛出UsernameNotFoundException异常 * @author wjqhuaxia * */ @Service public class UserServiceImpl implements UserService { @Autowired private PermissionMapper permissionMapper; @Autowired private RoleMapper roleMapper; @Autowired private UserMapper userMapper; @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userMapper.selectByUsername(username); if (user != null) { List<Permission> permissions = permissionMapper.findByUserId(user.getId()); List<GrantedAuthority> grantedAuthorities = new ArrayList <>(); for (Permission permission : permissions) { if (permission != null && permission.getPermissionname()!=null) { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getPermissionname()); grantedAuthorities.add(grantedAuthority); } } return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities); } else { throw new UsernameNotFoundException("username: " + username + " do not exist!"); } } @Transactional @Override public void userRegister(String username, String password) { User user = new User(); user.setUsername(passwordEncoder.encode(username)); user.setPassword(password); userMapper.insert(user); User rtnUser =userMapper.selectByUsername(username); //注册成功默认给用户的角色是user roleMapper.insertUserRole(rtnUser.getId(), 2); } }
3.3 MyFilterInvocationSecurityMetadataSource
自定义权限过滤器,继承了 SecurityMetadataSource(权限资源接口),过滤所有请求,核查这个请求需要的访问权限;主要实现Collection<ConfigAttribute> getAttributes(Object o)方法,此方法中可编写用户逻辑,根据用户预先设定的用户权限列表,返回访问此url需要的权限列表。
package com.example.demo.config; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 org.springframework.util.StringUtils; import com.example.demo.dao.PermissionMapper; import com.example.demo.model.Permission; /** * 自定义权限过滤器 * FilterInvocationSecurityMetadataSource(权限资源过滤器接口)继承了 SecurityMetadataSource(权限资源接口) * Spring Security是通过SecurityMetadataSource来加载访问时所需要的具体权限;Metadata是元数据的意思。 * 自定义权限资源过滤器,实现动态的权限验证 * 它的主要责任就是当访问一个url时,返回这个url所需要的访问权限 * @author wjqhuaxia * */ @Service public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private static final Logger log = LoggerFactory.getLogger(MyFilterInvocationSecurityMetadataSource.class); @Autowired private PermissionMapper permissionMapper; private HashMap<String, Collection<ConfigAttribute>> map = null; /** * 加载权限表中所有权限 */ public void loadResourceDefine() { map = new HashMap<String, Collection<ConfigAttribute>>(); List<Permission> permissions = permissionMapper.findAll(); for (Permission permission : permissions) { if(StringUtils.isEmpty(permission.getPermissionname())){ continue; } if(StringUtils.isEmpty(permission.getUrl())){ continue; } ConfigAttribute cfg = new SecurityConfig(permission.getPermissionname()); List<ConfigAttribute> list = new ArrayList<>(); list.add(cfg); // TODO:如果一个url对应多个权限,这里有问题 map.put(permission.getUrl(), list); } } /** * 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法, 用来判定用户 * 是否有此权限。如果不在权限表中则放行。 */ @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { if (map == null) { loadResourceDefine(); } // object 中包含用户请求的request的信息 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); for (Entry<String, Collection<ConfigAttribute>> entry : map.entrySet()) { String url = entry.getKey(); if (new AntPathRequestMatcher(url).matches(request)) { return map.get(url); } } /** * @Author: Galen * @Description: 如果本方法返回null的话,意味着当前这个请求不需要任何角色就能访问 * 此处做逻辑控制,如果没有匹配上的,返回一个默认具体权限,防止漏缺资源配置 **/ log.info("当前访问路径是{},这个url所需要的访问权限是{}", request.getRequestURL(), "ROLE_LOGIN"); return SecurityConfig.createList("ROLE_LOGIN"); } /** * 此处方法如果做了实现,返回了定义的权限资源列表, * Spring Security会在启动时校验每个ConfigAttribute是否配置正确, * 如果不需要校验,这里实现方法,方法体直接返回null即可 */ @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } /** * 方法返回类对象是否支持校验, * web项目一般使用FilterInvocation来判断,或者直接返回true */ @Override public boolean supports(Class<?> clazz) { return true; } }
3.4 AuthenticationAccessDeniedHandler
自定义权限决策管理器,需要实现AccessDecisionManager 的 void decide(Authentication auth, Object object, Collection<ConfigAttribute> cas) 方法,在上面的过滤器中,我们已经得到了访问此url需要的权限;那么,decide方法,先查询此用户当前拥有的权限,然后与上面过滤器核查出来的权限列表作对比,以此判断此用户是否具有这个访问权限,决定去留!所以顾名思义为权限决策器。
package com.example.demo.config; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * 拒签(403响应)处理器 * Denied是拒签的意思 * @author wjqhuaxia * */ @Component public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException { resp.setStatus(HttpServletResponse.SC_FORBIDDEN); resp.setContentType("application/json;charset=UTF-8"); PrintWriter out = resp.getWriter(); out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}"); out.flush(); out.close(); } }