Implementation of springboot integrating shiro multi authentication login function (account password login and mobile phone verification code login)
- 2021-10-25 06:49:52
- OfStack
1. First, create a new shiroConfig shiro configuration class with the following code:
@Configuration
public class SpringShiroConfig {
/**
* @param realms The interface collection is used here to implement the multi-authentication login
* @return
*/
@Bean
public SecurityManager securityManager(Collection<Realm> realms) {
DefaultWebSecurityManager sManager = new DefaultWebSecurityManager();
sManager.setRealms(realms);
return sManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager) {
ShiroFilterFactoryBean sfBean = new ShiroFilterFactoryBean();
sfBean.setSecurityManager(securityManager);
// If it is anonymous access, the location of the resource jump that cannot be accessed is accessed
sfBean.setLoginUrl("/index");
// Definition map Specify request filtering rules ( Which resources allow anonymous access , Which must authenticate access )
LinkedHashMap<String, String> map = new LinkedHashMap<>();
// Static resources allow anonymous access :"anon" Static resource authorization cannot be written static All the following openings should be static All folders below 1 A 1 An open, templates Likewise
//map Adj. key It can be the location of the file or the path of the request
map.put("/bower_components/**", "anon");
map.put("/json/**", "anon");
map.put("/pages", "anon");
map.put("/user/userPasswordLogin", "anon");
map.put("/user/login", "anon");
map.put("/user/reg", "anon");
// When accessing this path, you will not enter controller Will intercept and exit directly here, ask why, and want to request the process to go
map.put("/user/userLogout", "logout");
// Intercept all request paths except the above
map.put("/**", "user");
sfBean.setFilterChainDefinitionMap(map);
return sfBean;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
2. Write the implementation class of Realms, which inherits from AuthorizingRealm (this is to realize user name and password login). The code is as follows:
@Service
public class ShioUserRealm extends AuthorizingRealm {
// Injection userdao
@Autowired
private UserDao userDao;
/**
* Setting up the credential matcher
*
* @param credentialsMatcher
*/
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
/* Set up here MD5 Salt value encryption, you must use it here HashedCredentialsMatcher To have the following two methods */
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// Here is how to set the encryption
matcher.setHashAlgorithmName("MD5");
// Here is the number of times to set encryption
matcher.setHashIterations(2);
super.setCredentialsMatcher(matcher);
}
/**
* Here is the authorization set
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* Acquisition and encapsulation of authentication data are accomplished by this method. The bottom layer of the system transmits the authentication data to the authentication manager, and the authentication manager completes the authentication operation
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// First, determine whether this is the data from this token: we have divided it into UsernamePasswordToken ( shiro Provided to us.), UserPhoneToken
if (!(authenticationToken instanceof UsernamePasswordToken)) {
return null;
}
// Get controller Incoming data
UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
//upToken.setRememberMe(true);shiro Default to false, Is whether to remember my function
// Submitted here for the user username
String username = upToken.getUsername();
// De-data is more name Get the user's information
User user = userDao.findUserByUserName(username);
// Determine whether the database has this user
if (user == null) {
throw new UnknownAccountException();
}
// Determining whether the user's status is disabled ( Fields of the database )
if (user.getState() == 0) {
throw new LockedAccountException();
}
// Here is the salt value in the user information, and the salt value should be converted to ByteSource This type can only be used
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getSalt());
// Here is to give this user's information to shiro ( user For the user object, user.getPassword() Is the object to encrypt, credentialsSalt Is the salt value, getName() Current object)
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), credentialsSalt, getName());
return info;
}
}
3. At this time, the user's account password login can already be used. The controller code is as follows:
@RequestMapping("userPasswordLogin")
@ResponseBody
public JsonResult userPasswordLogin(String username, String password) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);
return new JsonResult("login Ok");
}
4. Let's realize SMS verification code login now:
4.1 Write UserPhoneToken first, and I put it in the same directory as l and springShiroConfig:
@Component
public class UserPhoneToken extends UsernamePasswordToken implements Serializable {
private static final long serialVersionUID = 6293390033867929958L;
// Mobile phone number
private String phoneNum;
// Parametric structure
public UserPhoneToken(){}
// Gets the value saved
@Override
public Object getPrincipal() {
if (phoneNum == null) {
return getUsername();
} else {
return getPhoneNum();
}
}
@Override
public Object getCredentials() {
if (phoneNum == null) {
return getPassword();
}else {
return "ok";
}
}
public UserPhoneToken(String phoneNum) {
this.phoneNum = phoneNum;
}
public UserPhoneToken(final String userName, final String password) {
super(userName, password);
}
public String getPhoneNum() {
return phoneNum;
}
public void setPhoneNum(String phoneNum) {
this.phoneNum = phoneNum;
}
@Override
public String toString() {
return "PhoneToken [PhoneNum=" + phoneNum + "]";
}
}
4.2 In writing shiroUserPhoneRealm, the code is as follows:
@Service
public class ShioUserPhoneRealm extends AuthorizingRealm {
@Autowired
private UserDao userDao;
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
// Here's CredentialsMatcher Adj. new The object of must be AllowAllCredentialsMatcher
CredentialsMatcher matcher = new AllowAllCredentialsMatcher();
super.setCredentialsMatcher(matcher);
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* Acquisition and encapsulation of authentication data are accomplished by this method. The bottom layer of the system transmits the authentication data to the authentication manager, and the authentication manager completes the authentication operation
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UserPhoneToken token = null;
if (authenticationToken instanceof UserPhoneToken) {
token = (UserPhoneToken) authenticationToken;
}else {
return null;
}
// Get the verification code I sent is stored in session Verification code and mobile phone number in
String verificationCode = (String) SecurityUtils.getSubject().getSession().getAttribute("verificationCode");
String phone = (String) SecurityUtils.getSubject().getSession().getAttribute("phone");
// Get controller Incoming data
String verificationCode1 = (String) token.getPrincipal();
// Go to the database to query the user information according to the mobile phone number
User user = userDao.findUserByUserPhone(phone);
if (StringUtils.isEmpty(verificationCode)) {
throw new ServiceException(" Network error ");
}
// Compare mobile phone numbers
if (!verificationCode.equals(verificationCode1)) {
throw new ServiceException(" Incorrect verification code ");
}
if (user == null) {
throw new UnknownAccountException();
}
if (user.getState() == 0) {
throw new LockedAccountException();
}
return new SimpleAuthenticationInfo(user,phone,getName());
}
}
4.3 Mobile phone number login verification has been basically completed: controller code is as follows:
@PostMapping("verificationCodeLogin")
@ResponseBody
public JsonResult verificationCodeLogin(String password) {
Subject subject = SecurityUtils.getSubject();
UserPhoneToken token = new UserPhoneToken(password);
subject.login(token);
return new JsonResult("login OK");
}
bug encountered during use
1.
org. apache. shiro. authc. UnknownAccountException: Realm [cn. tedu. wxacs. service. impl @ 768d8431] was unable to account the the submitted AuthenticationToken [org. apache. shiro. authc-Zhang 3, rememberMe=false].
This problem is mine because one of the implementation classes in Realm is not annotated, which I demonstrated here is @ Service annotated instead of ShiroUserRealm
2.
org.apache.shiro.authc.AuthenticationException: Authentication token of type [class org.apache.shiro.authc.UsernamePasswordToken] could not be authenticated by any configured realms. Please ensure that at least one realm can authenticate these tokens.
The problem here is that it should be User user = userDao. findUserByUserName (username) for my ShioUserRealm's AuthenticationInfo method; The problem occurred in this line of code, when debug, it was found that this sentence was error-guaranteed after execution
Reason: Because my application. yml file does not have the path to write the mapper file corresponding to dao
3. After the position of new SimpleAuthenticationInfo (user, phone, getName ()) in doGetAuthenticationInfo method of ShioUserPhoneRealm, you will report that the error should be ShioUserPhoneRealm. In this method, you did not set the object of new to AllowAllCredentialsMatcher ();
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
// Here's CredentialsMatcher Adj. new The object of must be AllowAllCredentialsMatcher
CredentialsMatcher matcher = new AllowAllCredentialsMatcher();
super.setCredentialsMatcher(matcher);
}
There are 1 points that need attention in the annotation. It is suggested to see. If the annotation is wrong, I hope to point out or contact me in the decentralized comments
Should be my limited knowledge, this method to achieve my current no problem, which is what is wrong with the place also hope that you point out, thank you!
jdk 8, spring version 2.2. 1 of boot, shiro version 1,. 4.1