对spring security 做个小结:
spring boot把spring mvc的配置文件改成了以代码的形式配置
WebSecurityConfig extends WebSecurityConfigurerAdapter
要继承这个类 这里面有很多的是父类的方法 重点看看这个方法:
protected void configure(HttpSecurity http) throws Exception {
/**
* 解决跨域
* 1、csrf 禁用,ajax跨域请求会被拦截判定失效 csrf.disable()
* 2、注册用户自定义的请求匹配器requireCsrfProtectionMatcher
*/
http.exceptionHandling().accessDeniedPage("/authExp").and().authorizeRequests()
.antMatchers("/message/*").permitAll().antMatchers("/error").permitAll()
.antMatchers("/**").hasRole("USER").and().formLogin().loginPage("/logon").permitAll()
.and().logout().invalidateHttpSession(true).logoutUrl("/logout").and().csrf()
.requireCsrfProtectionMatcher(userRequiresCsrfMatcher()).and().rememberMe().key(remKey)
.rememberMeServices(rememberMeServices()).and().sessionManagement()
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
.invalidSessionUrl("/logon").and()
.addFilterAt(ajaxLoginFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterAt(mySecurityFilter(), FilterSecurityInterceptor.class);
}
配置的很多url 和mvc 里面xml配置是一样的道理 没有权限 就访问/authExp 配置不需要的权限访问 配置基本角色 默认加 “ROLE_” 以及一些filter
下面需要设置要被控制的url
public Map<String, Collection<ConfigAttribute>> getAllResources() {
Map<String, Collection<ConfigAttribute>> map = new HashMap<>();
//获取url资源
List<SysResources> listDB = sysResourcesDao.getUrlResources();
for (SysResources sysResources : listDB) {
Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
//以权限名封装为Spring的security Object
ConfigAttribute config = new SecurityConfig("ROLE_" + sysResources.getResourceId()
+ "_" + sysResources.getResourceName());
configAttributes.add(config);
map.put(sysResources.getResourcePath(), configAttributes);
}
return map;
}
和 用户自己的资源
@Override
public Set<GrantedAuthority> getAuthorities(LogonInfo user) {
//SET去重
Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();
//这里要查数据库,由于在remember me时 要先调用此方法 然而当前登录用户还没有初始化权限信息而报错
List<SysResources> listSysresourcesDB = sysResourcesDao.getUrlResourcesByUid(user.getuId());
for (SysResources SysResources : listSysresourcesDB) {
authSet.add(new SimpleGrantedAuthority("ROLE_" + SysResources.getResourceId() + "_"
+ SysResources.getResourceName()));
}
//给所有登录用户自动增加ROLE_USER权限
authSet.add(new SimpleGrantedAuthority("ROLE_USER"));
return authSet;
}
上面的 ROLE_USER一定要加
AjaxAuthenticationFailureHandler
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException,
ServletException {
NetworkUtil.responseJSONMsg(response, ResultInfo.createResult(CodeEnum.ERROR_10001));
}
AjaxAuthenticationProvider
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response)
throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: "
+ request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
logger.debug(">>>attemptAuthentication()-验证用户账号密码<<<");
username = username.trim();
//根据用户名到数据库取出用户数据
LogonInfo user = springSecurityService.getByNameWithNoAuth(username);
//验证登录用户信息
if (user == null) {
logger.error("用户不存在!");
throw new AuthenticationServiceException("用户不存在!");
}
if (!user.getLoginPwd().equals(DigestPass.getDigestPassWord(password))) {
logger.error("密码输入错误!");
throw new AuthenticationServiceException("密码输入错误!");
}
if (user.getDisableFlag().equals(IGlobalConstant.DISABLED)) {
logger.error("用户失效,无法登录!");
throw new AuthenticationServiceException("用户失效,无法登录!");
}
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, DigestPass.getDigestPassWord(password));
//验证用户信息
springSecurityService.validateUser(user);
//登录之后具体操作,后期用service统一处理
springSecurityService.initData(user);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
AjaxAuthenticationSuccessHandler
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException,
ServletException {
NetworkUtil.responseJSONMsg(response, ResultInfo.createResult(CodeEnum.SUCCESS));
}
MyAccessDecisionManager
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException {
logger.info(">>>decide()-判断用户是否具有访问该url权限<<<");
if (configAttributes == null) {
return;
}
//所请求的资源拥有的权限(一个资源对多个权限)
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()) {
ConfigAttribute configAttribute = iterator.next();
//访问所请求资源所需要的权限
String needPermission = configAttribute.getAttribute();
logger.info("needPermission is " + needPermission);
//用户所拥有的权限authentication
for (GrantedAuthority ga : authentication.getAuthorities()) {
if (needPermission.equals(ga.getAuthority())) {
return;
}
}
}
logger.error(">>>decide()- 没有权限访问!<<<");
//没有权限让我们去捕捉
throw new AccessDeniedException(" 没有权限访问!");
}
MySecurityFilter
MySecurityMetadataSource
/**
* 返回该次请求资源所需要的权限,与数据库中的url进行匹配,如果匹配成功则进行权限验证,因此这方法决定url是否需要权限验证
* 即调用@see MyAccessDecisionManager#decide(org.springframework.security.core.Authentication, Object, Collection)
* @param object
* @return
* @throws IllegalArgumentException
* @see org.springframework.security.access.SecurityMetadataSource#getAttributes(java.lang.Object)
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
//从缓存取所有资源
Map<String, Collection<ConfigAttribute>> resourceMap = ehCacheService.getAllResources();
String requestUrl = ((FilterInvocation) object).getRequestUrl();
logger.debug(">>>requestUrl is {} <<<", requestUrl);
Collection<ConfigAttribute> string = null;
// 检测请求与当前资源匹配的正确性
Iterator<String> iterator = resourceMap.keySet().iterator();
while (iterator.hasNext()) {
String uri = iterator.next();
boolean flag = ehCacheService.getMatchResult(uri, requestUrl);
logger.debug("{} {}", uri, flag);
if (flag) {
string = resourceMap.get(uri);
break;
}
}
return string;
}
MyTokenBasedRememberMeServices
@Override
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
HttpServletResponse response) {
if (cookieTokens.length != 3) {
throw new InvalidCookieException("Cookie token did not contain 3"
+ " tokens, but contained '"
+ Arrays.asList(cookieTokens) + "'");
}
long tokenExpiryTime;
try {
tokenExpiryTime = new Long(cookieTokens[1]).longValue();
} catch (NumberFormatException nfe) {
throw new InvalidCookieException(
"Cookie token[1] did not contain a valid number (contained '" + cookieTokens[1]
+ "')");
}
if (isTokenExpired(tokenExpiryTime)) {
throw new InvalidCookieException("Cookie token[1] has expired (expired on '"
+ new Date(tokenExpiryTime) + "'; current time is '"
+ new Date() + "')");
}
String ipaddress = "";
try {
ipaddress = NetworkUtil.getIpAddress(request);
} catch (IOException e) {
logger.error("Error{}", e);
}
// Check the user exists.
// Defer lookup until after expiry time checked, to possibly avoid expensive database call.
UserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]);
// Check signature of token matches remaining details.
// Must do this after user lookup, as we need the DAO-derived password.
// If efficiency was a major issue, just add in a UserCache implementation,
// but recall that this method is usually only called once per HttpSession - if the token is valid,
// it will cause SecurityContextHolder population, whilst if invalid, will cause the cookie to be cancelled.
String expectedTokenSignature = makeTokenSignature(tokenExpiryTime,
userDetails.getUsername(), userDetails.getPassword(), ipaddress);
if (!equals(expectedTokenSignature, cookieTokens[2])) {
throw new InvalidCookieException("Cookie token[2] contained signature '"
+ cookieTokens[2] + "' but expected '"
+ expectedTokenSignature + "'");
}
LogonInfo users = springSecurityService.getByNameWithNoAuth(cookieTokens[0]);
springSecurityService.initData(users);
return userDetails;
}
UserDetailServiceImpl
@Override
public UserDetails loadUserByUsername(String loginName) throws UsernameNotFoundException {
logger.debug(">>>loadUserByUsername()-获取用户信息<<<");
//获取用户信息
LogonInfo user = springSecurityService.getByNameWithNoAuth(loginName);
if (user == null) {
logger.debug(loginName + "用户不存在!");
throw new UsernameNotFoundException(loginName + "用户不存在!");
}
//封装成spring security的user
return new User(user.getLoginName(), user.getLoginPwd(), true, true, true, true,
ehCacheService.getAuthorities(user));
}
然后就是这几个类 第一个是登录失败进的、第二个是控制登录,初始化user数据到session的 第三个是登录成功过后进入、4权限判断 MySecurityMetadataSource(判断要进入权限验证的url,如果不匹配就不进 MyAccessDecisionManager 的 decide方法 ) 记住登录、封装成spring security的user
1.用户的URL被MySecurityFilter拦截,并交给MySecurityMetadataSource
2.在MySecurityMetadataSource中,将拦截到的url与数据库中所有激活的Resource的url进行match(有正则)
如果没有找到匹配成功的,则直接放行,不必经过步骤3。
如果有找到匹配项,返回Resource对应的重新封装的值如 "ROLE_" + sysResources.getResourceId()
+ "_" + sysResources.getResourceName()",然后交给MyAccessDecisionManager
(PS:用户登录的时候,系统会查找用户对应的角色以及这些角色对应的资源,并将这些资源重新封装,
即从用户->角色->资源整合成用户->资源这种模式,然后直接封装用户可以访问哪些资源,只保存封装后的资源名称如:"ROLE_" + sysResources.getResourceId()
+ "_" + sysResources.getResourceName()")
3.MyAccessDecisionManager会在当前登录用户的重新封装后的权限列表里面查找资源名"ROLE_" + sysResources.getResourceId()
+ "_" + sysResources.getResourceName()",
如找到,则放行。
如没有找到,则返回配置文件中配置的权限验证失败后的默认放回页面。
MySecurityFilter 在WebSecurityConfig这里定义的 .addFilterAt(mySecurityFilter(), FilterSecurityInterceptor.class);
还可以定义跨域拦截,在yml中配 指定域名访问
到此结束