Blog

  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

springboot-shiro

发表于 2019-09-15 分类于 SpringBoot , Shiro 阅读次数:
本文字数: 17k

Shiro相关过滤器

ShiroConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ShiroConfig {

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//自定义拦截配置,OPTIONS请求直接返回true
// HashMap<String, Filter> filters = new HashMap<>();
// filters.put("custom", new ShiroUserFilter());
// shiroFilterFactoryBean.setFilters(filters);

filterChainDefinitionMap.put("/admin/login", "anon");
filterChainDefinitionMap.put("/admin/auth/401", "anon");
filterChainDefinitionMap.put("/admin/auth/index", "anon");
filterChainDefinitionMap.put("/admin/auth/403", "anon");
filterChainDefinitionMap.put("/admin/index/index", "anon");
filterChainDefinitionMap.put("/admin/test1", "authc");
filterChainDefinitionMap.put("/admin/test2", "roles[admin]");
filterChainDefinitionMap.put("/**", "anon");

shiroFilterFactoryBean.setLoginUrl("/admin/auth/401");
shiroFilterFactoryBean.setSuccessUrl("/admin/auth/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/admin/auth/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public Realm realm() {
return new AdminAuthorizingRealm();
}
}

登录流程

1.login

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@PostMapping("/login")
public Object login(String username, String password, HttpServletRequest request) {
Subject currentUser = SecurityUtils.getSubject();
try {
currentUser.login(new UsernamePasswordToken(username, password));
} catch (UnknownAccountException uae) {
return "用户帐号或密码不正确";
} catch (LockedAccountException lae) {
return "用户帐号已锁定不可用";

} catch (AuthenticationException ae) {
return "认证失败";
}
return "login success";
}
  • 通过SecurityUtils.getSubject获取当前主体
  • 然后将用户名和密码分装为UsernamePasswordToken
  • 调用login方法

2.DefaultSecurityManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);
} catch (AuthenticationException ae) {
try {
onFailedLogin(token, ae, subject);
} catch (Exception e) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an " +
"exception. Logging and propagating original AuthenticationException.", e);
}
}
throw ae; //propagate
}

Subject loggedIn = createSubject(token, info, subject);

onSuccessfulLogin(token, info, loggedIn);

return loggedIn;
}
  • 调用authenticate(token)

3.AbstractAuthenticator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {

if (token == null) {
throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
}

log.trace("Authentication attempt received for token [{}]", token);

AuthenticationInfo info;
try {
info = doAuthenticate(token);
if (info == null) {
throw new AuthenticationException(msg);
}
} catch (Throwable t) {
AuthenticationException ae = null;
if (t instanceof AuthenticationException) {
ae = (AuthenticationException) t;
}
if (ae == null) {
ae = new AuthenticationException(msg, t);
if (log.isWarnEnabled())
log.warn(msg, t);
}
try {
notifyFailure(token, ae);
} catch (Throwable t2) {
if (log.isWarnEnabled()) {
log.warn(msg, t2);
}
}
throw ae;
}
notifySuccess(token, info);

return info;
}

4.ModularRealmAuthenticator

1
2
3
4
5
6
7
8
9
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}

5.AuthenticatingRealm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
//获取用户信息
info = doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}

if (info != null) {
//此处将获取的用户信息和用户提交的信息做比对
assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}

return info;
}
  • 先从内存中获取
  • 如果内存中为null,就执行自定义的realm中的doGetAuthenticationInfo方法进行认证。

6.AdminAuthorizingRealm(自定义Realm)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class AdminAuthorizingRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
Set<String> roles = new HashSet<>(); //从数据库中查询出roles
Set<String> permissions = new HashSet<>();//从数据库中查询出permissions
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roles);
info.setStringPermissions(permissions);
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
String password = new String(upToken.getPassword());

if (StringUtils.isEmpty(username)) {
throw new AccountException("用户名不能为空");
}
if (StringUtils.isEmpty(password)) {
throw new AccountException("密码不能为空");
}

String password2 = "password2";//从数据库中查找

//principal:用户主体(代表用户,可用id,username,或者一个实例对象)
//credentials:用户凭证(使用从数据库中查询出来的密码)
//realmName:获取主体和凭据的域:调用父类方法getName()即可获得。
//这里生成的SimpleAuthenticationInfo
return new SimpleAuthenticationInfo(username, password2, getName());
}
}
  • 这里会从数据库中根据username查询出对象,并生成一个SimpleAuthenticationInfo返回。用于和用户提交的username和password做比对。(为什么这里不直接判断一下密码是否正确呢?)

7.返回了SimpleAuthenticationInfo之后,继续返回到AuthenticatingRealm,进行用户信息校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {
//not successful - throw an exception to indicate this:
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
} else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
"credentials during authentication. If you do not wish for credentials to be examined, you " +
"can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}

8.SimpleCredentialsMatcher

1
2
3
4
5
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = getCredentials(token);
Object accountCredentials = getCredentials(info);
return equals(tokenCredentials, accountCredentials);
}
  • 先获取用户提交的password
  • 再获取从数据库中查询出来的password
  • 进行校验

9.校验成功之后,回到最初的DefaultSecurityManager

1
2
3
4
5
Subject loggedIn = createSubject(token, info, subject);

onSuccessfulLogin(token, info, loggedIn);

return loggedIn;
  • 这里会创建一个Subject(主体)

authc认证

1
filterChainDefinitionMap.put("/admin/test1", "authc");

1.AccessControlFilter

1
2
3
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}

2.AuthenticatingFilter

1
2
3
4
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return super.isAccessAllowed(request, response, mappedValue) ||
(!isLoginRequest(request, response) && isPermissive(mappedValue));
}

3.AuthenticationFilter

1
2
3
4
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
return subject.isAuthenticated();
}
  • 首先通过isAccessAllowed方法判断是否已经授权
  • 如果没有授权,就调用FormAuthenticationFilter中的onAccessDenied方法。
    • 如果是login请求:就执行登录
    • 如果不是,就重定向到之前设定的loginUrl

4.FormAuthenticationFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) {
if (isLoginSubmission(request, response)) {
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
return executeLogin(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
//allow them to see the login page ;)
return true;
}
} else {
if (log.isTraceEnabled()) {
log.trace("Attempting to access a path which requires authentication. Forwarding to the " +
"Authentication url [" + getLoginUrl() + "]");
}

saveRequestAndRedirectToLogin(request, response);
return false;
}
}

授权过程

1
filterChainDefinitionMap.put("/admin/test2", "roles[admin]");

1.AccessControlFilter

1
2
3
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}

2.RolesAuthorizationFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {

Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;

if (rolesArray == null || rolesArray.length == 0) {
//no roles specified, so nothing to check - allow access.
return true;
}

Set<String> roles = CollectionUtils.asSet(rolesArray);
return subject.hasAllRoles(roles);
}
  • 获取需要的角色

3.DelegatingSubject

1
2
3
public boolean hasAllRoles(Collection<String> roleIdentifiers) {
return hasPrincipals() && securityManager.hasAllRoles(getPrincipals(), roleIdentifiers);
}

4.AuthorzingSecurityManager

1
2
3
public boolean hasAllRoles(PrincipalCollection principals, Collection<String> roleIdentifiers) {
return this.authorizer.hasAllRoles(principals, roleIdentifiers);
}

4.ModularRealmAuthorizer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public boolean hasAllRoles(PrincipalCollection principals, Collection<String> roleIdentifiers) {
assertRealmsConfigured();
for (String roleIdentifier : roleIdentifiers) {
if (!hasRole(principals, roleIdentifier)) {
return false;
}
}
return true;
}

public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
if (((Authorizer) realm).hasRole(principals, roleIdentifier)) {
return true;
}
}
return false;
}
  • 这里会遍历realms

5.AuthorizingRealm

1
2
3
4
5
6
7
public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
AuthorizationInfo info = getAuthorizationInfo(principal);
return hasRole(roleIdentifier, info);
}
protected boolean hasRole(String roleIdentifier, AuthorizationInfo info) {
return info != null && info.getRoles() != null && info.getRoles().contains(roleIdentifier);
}

和之前的认证一样,也会先从内存中寻找,没有的话就从自定义的realm中调用doGetAuthorizationInfo获取授权信息。
6.AdminAuthorizingRealm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AdminAuthorizingRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
Set<String> roles = new HashSet<>(); //从数据库中查询出roles
Set<String> permissions = new HashSet<>();//从数据库中查询出permissions
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roles);
info.setStringPermissions(permissions);
return info;
}
}
  • 主要是RolesAuthorizationFilter中的isAccessAllowed方法、

加密(加盐)

1
2
3
4
5
6
7
8
9
@Bean
public Realm realm() {
AdminAuthorizingRealm realm = new AdminAuthorizingRealm();
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("SHA-256");//加密算法
credentialsMatcher.setHashIterations(1024);//加密次数
realm.setCredentialsMatcher(credentialsMatcher);
return realm;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
String password = new String(upToken.getPassword());

if (StringUtils.isEmpty(username)) {
throw new AccountException("用户名不能为空");
}
if (StringUtils.isEmpty(password)) {
throw new AccountException("密码不能为空");
}

//19e0ab03098e9ffb6485df82d85307e998b799d23c3d77b9e47fce4bb2575b6d
String password2 = "19e0ab03098e9ffb6485df82d85307e998b799d23c3d77b9e47fce4bb2575b6d";//从数据库中查找

//principal:用户主体(代表用户,可用id,username,或者一个实例对象)
//credentials:用户凭证(使用从数据库中查询出来的密码)
//realmName:获取主体和凭据的域:调用父类方法getName()即可获得。
//这里生成的SimpleAuthenticationInfo
//使用username当做salt
ByteSource salt = ByteSource.Util.bytes(username);
return new SimpleAuthenticationInfo(username, password2, salt,getName());
}
  • 在自定义realm的父类AuthenticatingRealm中,有个CredentialsMatcher credentialsMatcher属性,这就是指定加密算法的属性,默认为SimpleCredentialsMatcher。
  • 在生成SimpleAuthenticatinInfo对象时,将salt传入。
  • HashedCredentialsMatcher中的hashProvidedCredentials方法中会使用该salt为用户提交的密码进行加密(和数据库中查询出来的已加密的密码比较)
    HashedCredentialsMatcher
    1
    2
    3
    4
    5
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    Object tokenHashedCredentials = hashProvidedCredentials(token, info);
    Object accountCredentials = getCredentials(info);
    return equals(tokenHashedCredentials, accountCredentials);
    }
1
2
3
4
5
6
7
8
9
10
11
12
protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
Object salt = null;
if (info instanceof SaltedAuthenticationInfo) {
salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
} else {
//retain 1.0 backwards compatibility:
if (isHashSalted()) {
salt = getSalt(token);
}
}
return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected Object getCredentials(AuthenticationInfo info) {
Object credentials = info.getCredentials();

byte[] storedBytes = toBytes(credentials);

if (credentials instanceof String || credentials instanceof char[]) {
//account.credentials were a char[] or String, so
//we need to do text decoding first:
if (isStoredCredentialsHexEncoded()) {
storedBytes = Hex.decode(storedBytes);
} else {
storedBytes = Base64.decode(storedBytes);
}
}
AbstractHash hash = newHashInstance();
hash.setBytes(storedBytes);
return hash;
}

------ 已触及底线感谢您的阅读 ------
麻辣香锅不要辣 微信支付

微信支付

  • 本文作者: 麻辣香锅不要辣
  • 本文链接: https://http://ybhub.gitee.io/2019/09/15/springboot-shiro/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
# SpringBoot # Shiro
mybatis_generator
docker
  • 文章目录
  • 站点概览
麻辣香锅不要辣

麻辣香锅不要辣

21 日志
11 分类
20 标签
GitHub 简书
  1. 1. Shiro相关过滤器
  2. 2. ShiroConfig
  3. 3. 登录流程
  4. 4. authc认证
  5. 5. 授权过程
  6. 6. 加密(加盐)
© 2019 – 2020 麻辣香锅不要辣 | 站点总字数: 20.4k字
|
0%