SpringBoot is used as the main framework, and Shiro framework is used as the authentication and authorization module.
Before spring boot + Shiro + password encryption, I stepped on a lot of holes, so I walked through the Shiro process and made a record.
1. Introduce Shiro first
Everyone who has used Shiro knows that Shiro uses decorator mode internally. The big SecurityManager interface inherits three interfaces: Authenticator authentication, Authorizer authorization and SessionManager session management,
Its implementation class is well understood according to its name. It should be noted that RealmSecurityManager and WebSecurityManager. WebSecurityManager is an interface. Its implementation class Shiro only provides one: DefaultWebSecurityManager, which is usually enough. Open this class to find a familiar Realm
In the constructor, this class needs a Realm, and then check the setRealm method. It is found that it has gone to the RealmSecurityManager. It can be roughly imagined that the DefaultWebSecurityManager inherits from the RealmSecurityManager.
In fact, it is true that RealmSecurityManager is an abstract class, and the parent class of RealmSecurityManager, cacheingsecuritymanager, is also an abstract class. We all know that abstract classes define the specification of a class of things or behavior processes. Let's look at the subclass implementation of RealmSecurityManager:
You know, authorization management, authentication management, session management, and the DefaultWebSecurityManager provided by Shiro all rely on real.
Let's continue with Realm:
As an interface, Realm has implementation classes under its command. Combined with Shiro's design on SecurityManager, it is easy to think that there must be abstract classes and default implementation classes in these classes. Also see cacheingrealm. In the design of SecurityManager, Cache is used as the abstract parent class of RealmManager. It must also be here:
Let's look at its subclasses. Shiro is the security framework for authentication, and authentication should be in the next step of authentication, so click AuthenticatingRealm first:
It's an abstract class, which is easy to understand. This abstract class must regulate Shiro's authentication steps or behaviors. See authentication authoringrealm:
It is still an abstract class and inherits from authenticated Realm:
You can see that Realm inherits the authorized Realm --- authorizing Realm
This is true in the actual development. We added a custom Realm to write authentication and authorization logic, and the login module passed org.apache.shiro.subject.Subject#login As the entrance, the big head SecurityManager is responsible for calling the Realm, and the final authentication and authentication module will go to our customized Realm.
Shiro introduced Wuwu slag to be here for the time being.
2. What to start integrating:
Add maven dependency:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro-spring}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<shiro-spring>1.8.0</shiro-spring>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
Add ShiroConfig configuration class:
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* User: Pfatman
* Date: 2021/11/9
* Time: 16:31
* Description: ShiroConfig
*/
@Slf4j
@Configuration
public class ShiroConfig {
@Value("shiro_loginPage:login")
private String loginPage;
/**
* Permission management is mainly used to configure the management authentication of realm
* @return
*/
@Bean
public SecurityManager securityManager(){
return new DefaultWebSecurityManager();
}
/**
* Dealing with intercepting resources
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
ShiroFilterFactoryBean factoryBean=new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
factoryBean.setLoginUrl(loginPage);
Map<String,String> map=new LinkedHashMap<>();
map.put("/static/**","anon");
map.put("/logout","logout");
factoryBean.setFilterChainDefinitionMap(map);
return factoryBean;
}
/**
* Shiro Bean life cycle
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* Shiro Agent enhancements provided
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* Authorization attribute enhancement
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
AuthorizationAttributeSourceAdvisor attributeSourceAdvisor=new AuthorizationAttributeSourceAdvisor();
attributeSourceAdvisor.setSecurityManager(securityManager());
return attributeSourceAdvisor;
}
}
two point one Throw a question:
In the strict sense of Shiro's Config above, there is a little less, that is, the custom Realm. When introducing Shiro, we saw that the constructor in the SecurityManager has Realm, but the SecurityManager is configured in the above configuration. Here is a direct return new
DefaultWebSecurityManager();
@Bean
public Realm realm(){
Realm realm = new MyRealm();
return realm;
}
@Bean public SecurityManager securityManager(Realm realm){ return new DefaultWebSecurityManager(realm); }
However, setting the Realm for SecurityManager in the above way may cause a problem. If there are objects or parameters in the custom Realm that depend on other injected beans, the properties injected through @ Autowired in the Realm may be null. This is because Shiro's beans start initializing other beans after initialization, that is, SecurityManager When real initializes a bean, other beans are not initialized and are null. If we directly insert a new Realm when constructing the bean of SecurityManager in the above way, the attribute injected through @ Autowired in MyRealm will be null.
2.2 how to solve:
The problem that the injection attribute in the Realm is empty usually occurs when Shiro's beans have been fully initialized before other beans are loaded. From this point of view, take our customized Realm as a Bean and initialize it by the Spring container, but this will lead to the absence of the Realm attribute in the SecurityManager Bean configured in ShiroConfig. Then the problem becomes to solve the problem of injecting our Realm into SecurityManager:
1. Inject SecurityManager into the custom Realm, and set the SecurityManager property Realm to this:
@Slf4j
@Service("wencharRealm")
public class WencharRealm extends AuthorizingRealm {
@Autowired
ILoginUserInfoService loginUserInfoService;
@Autowired
public WencharRealm(WencharCredentialsMatcher matcher){
super.setCredentialsMatcher(matcher);
}
@Autowired
private void webSecurityManager(SecurityManager securityManager) {
if (securityManager instanceof DefaultWebSecurityManager) {
log.info("==by DefaultWebSecurityManager set up Realm==");
DefaultWebSecurityManager webSecurityManager = (DefaultWebSecurityManager) securityManager;
webSecurityManager.setRealm(this);
}
}
/**
* to grant authorization
*
* @param principalCollection
* @return
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
}
/**
* authentication
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
}
}
2. Personally, I don't recommend that Realm, as a Bean, set Realm for SecurityManager after the Spring container is fully initialized, or use @ PostConstruct annotation.
ShiroConfig implements the implements applicationlistener < contextrefreshedevent > interface and assigns a value to the SecurityManager during refresh, but this is not as direct as the first method.
I feel that although it can realize the function, it does destroy the Bean process.
above.
3. Password comparator: CredentialsMatcher
Another supplementary content is the password verifier provided by Shiro, including encryption algorithm and encryption times
Customize a password verifier:
@Component
public class WencharCredentialsMatcher extends HashedCredentialsMatcher {
@Value("${REAL_SALTCOUNT:1024}")
private int saltCount;
@Override
public int getHashIterations() {
return saltCount;
}
@Override
public void setHashAlgorithmName(String hashAlgorithmName) {
super.setHashAlgorithmName(Md5Hash.ALGORITHM_NAME);
}
}
Note: the above user-defined password comparator inherits from HashedCredentialsMatcher, sets the encryption times to 1024 by default, and the encryption algorithm is Md5
In this way, the Realm needs to correspond to the login entry subject.login (), such as password, salt, etc.
Login verification:
Subject subject = SecurityUtils.getSubject();
try {
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
loginUser.getLoginName(),
loginUser.getLoginPwd());
subject.login(usernamePasswordToken);
} catch (AuthenticationException e) {
log.debug("===loginUser failed login==[{}]",loginUser);
return ResponseVo.failResponse("Username or password incorrect ");
}
Authentication verification in Realm:
/**
* authentication
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String userName = authenticationToken.getPrincipal().toString();
LoginUserVo loginUserVo = userInfoService.queryUserLoginInfo(userName);
return new SimpleAuthenticationInfo(loginUserVo.getAccountId(),
loginUserVo.getPassword(),
ByteSource.Util.bytes(loginUserVo.getSalt()),
getWencharRealmName());
}
Authentication and Subject.login(token) in Realm; It can be distinguished in this way. The user name, password before encryption and salt are transmitted in the token. These data will go through the salt encryption algorithm according to the parameters in the password comparator in the SecurityManager and the salt value in the AuthenticationInfo transmitted in the real, and then compare with the userName and password in the real.
above