“This is the 22nd day of my participation in the First Text Challenge 2022. For details, see: First Text Challenge 2022”. The realization steps are as follows:
1. Import Maven dependencies into the project
< the dependency > < groupId > org, apache shiro < / groupId > < artifactId > shiro - core < / artifactId > < version > 1.4.0 < / version > </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> The < version > 1.4.0 < / version > < / dependency >Copy the code
Shiro’s core configuration classes and code
- Custom (token entity) tokens
package com.ratel.fast.modules.sys.oauth2;
import org.apache.shiro.authc.AuthenticationToken;
/** * token */
public class OAuth2Token implements AuthenticationToken {
private String token;
public OAuth2Token(String token){
this.token = token;
}
@Override
public String getPrincipal(a) {
return token;
}
@Override
public Object getCredentials(a) {
returntoken; }}Copy the code
- Token generation tool
package com.ratel.fast.modules.sys.oauth2;
import com.ratel.fast.common.exception.RRException;
import java.security.MessageDigest;
import java.util.UUID;
/** * Generate token */
public class TokenGenerator {
public static String generateValue(a) {
return generateValue(UUID.randomUUID().toString());
}
private static final char[] hexCode = "0123456789abcdef".toCharArray();
public static String toHexString(byte[] data) {
if(data == null) {
return null;
}
StringBuilder r = new StringBuilder(data.length*2);
for ( byte b : data) {
r.append(hexCode[(b >> 4) & 0xF]);
r.append(hexCode[(b & 0xF)]);
}
return r.toString();
}
public static String generateValue(String param) {
try {
MessageDigest algorithm = MessageDigest.getInstance("MD5");
algorithm.reset();
algorithm.update(param.getBytes());
byte[] messageDigest = algorithm.digest();
return toHexString(messageDigest);
} catch (Exception e) {
throw new RRException("Token generation failed", e); }}}Copy the code
3. Customize the authentication source
package com.ratel.fast.modules.sys.oauth2;
import com.ratel.fast.modules.sys.service.ShiroService;
import com.ratel.fast.modules.sys.entity.SysUserEntity;
import com.ratel.fast.modules.sys.entity.SysUserTokenEntity;
import com.ratel.fast.modules.sys.service.ShiroService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Set;
/** ** authentication ** */
@Component
public class OAuth2Realm extends AuthorizingRealm {
@Autowired
private ShiroService shiroService;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof OAuth2Token;
}
/** * authorization (called when verifying permissions) * the front end is in the request band@RequiresPermissionsThe method doGetAuthorizationInfo is called when annotating methods
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SysUserEntity user = (SysUserEntity)principals.getPrimaryPrincipal();
Long userId = user.getUserId();
// List of user permissions
Set<String> permsSet = shiroService.getUserPermissions(userId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}
/** * Authentication (called on login) * This method is called on every request to verify that the token is invalid and the user is locked */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal();
// Query user information according to accessToken
SysUserTokenEntity tokenEntity = shiroService.queryByToken(accessToken);
/ / token expires
if(tokenEntity == null || tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()){
throw new IncorrectCredentialsException("Token invalid, please log in again");
}
// Query user information
SysUserEntity user = shiroService.queryUser(tokenEntity.getUserId());
// The account is locked
if(user.getStatus() == 0) {throw new LockedAccountException("Account has been locked. Please contact your administrator.");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, getName());
returninfo; }}Copy the code
4. Custom filters
package com.ratel.fast.modules.sys.oauth2;
import com.google.gson.Gson;
import com.ratel.fast.common.utils.HttpContextUtils;
import com.ratel.fast.common.utils.R;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/** * oauth2 filter ** */
public class OAuth2Filter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
// Get the request token
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
return null;
}
return new OAuth2Token(token);
}
/** * Check whether the user has logged in. * If the request is options, pass. Otherwise, call onAccessDenied for token authentication *@param request
* @param response
* @param mappedValue
* @return* /
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())){
return true;
}
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
// Get the request token, if the token does not exist, return 401
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Credentials"."true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
String json = new Gson().toJson(R.error(HttpStatus.SC_UNAUTHORIZED, "invalid token"));
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json; charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials"."true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
try {
// Handle login failure exceptions
Throwable throwable = e.getCause() == null ? e : e.getCause();
R r = R.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());
String json = new Gson().toJson(r);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
}
/** * Get the requested token */
private String getRequestToken(HttpServletRequest httpRequest){
// Get the token from the header
String token = httpRequest.getHeader("token");
// If no token exists in the header, the token is obtained from the argument
if(StringUtils.isBlank(token)){
token = httpRequest.getParameter("token");
}
returntoken; }}Copy the code
5. Core configuration classes
package com.ratel.fast.config;
import com.ratel.fast.modules.sys.oauth2.OAuth2Filter;
import com.ratel.fast.modules.sys.oauth2.OAuth2Realm;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/** * Shiro configuration ** */
@Configuration
public class ShiroConfig {
@Bean("securityManager")
public SecurityManager securityManager(OAuth2Realm oAuth2Realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(oAuth2Realm);
securityManager.setRememberMeManager(null);
return securityManager;
}
/** * The name of this bean must be shiroFilter, otherwise an error will be reported at startup *@BeanThe parentheses after ("shiroFilter") may not be the name of the bean with the spring default method name *@param securityManager
* @return* /
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
/ / request filtering
Map<String, Filter> filters = new HashMap<>();
// Add a custom filter
filters.put("oauth2".new OAuth2Filter());
shiroFilter.setFilters(filters);
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/webjars/**"."anon");
filterMap.put("/druid/**"."anon");
filterMap.put("/app/**"."anon");
filterMap.put("/sys/login"."anon");
filterMap.put("/swagger/**"."anon");
filterMap.put("/v2/api-docs"."anon");
filterMap.put("/swagger-ui.html"."anon");
filterMap.put("/swagger-resources/**"."anon");
filterMap.put("/captcha.jpg"."anon");
filterMap.put("/aaa.txt"."anon");
// Use custom filters to block all requests except above
filterMap.put("/ * *"."oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(a) {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
returnadvisor; }}Copy the code
6. User token class (used for storage in the database)
package com.ratel.fast.modules.sys.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/** * System user Token ** */
@Data
@TableName("sys_user_token")
public class SysUserTokenEntity implements Serializable {
private static final long serialVersionUID = 1L;
/ / user ID
@TableId(type = IdType.INPUT)
private Long userId;
//token
private String token;
// Expiration time
private Date expireTime;
// Update time
private Date updateTime;
}
Copy the code
- Controller used by the user to log in
package com.ratel.fast.modules.sys.controller;
import com.ratel.fast.common.utils.R;
import com.ratel.fast.modules.sys.entity.SysUserEntity;
import com.ratel.fast.modules.sys.form.SysLoginForm;
import com.ratel.fast.modules.sys.service.SysCaptchaService;
import com.ratel.fast.modules.sys.service.SysUserService;
import com.ratel.fast.modules.sys.service.SysUserTokenService;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Map;
/** * login related ** */
@RestController
public class SysLoginController extends AbstractController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysUserTokenService sysUserTokenService;
@Autowired
private SysCaptchaService sysCaptchaService;
/** * Verification code */
@GetMapping("captcha.jpg")
public void captcha(HttpServletResponse response, String uuid)throws IOException {
response.setHeader("Cache-Control"."no-store, no-cache");
response.setContentType("image/jpeg");
// Get the image verification code
BufferedImage image = sysCaptchaService.getCaptcha(uuid);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(image, "jpg", out);
IOUtils.closeQuietly(out);
}
/** * login */
@PostMapping("/sys/login")
public Map<String, Object> login(@RequestBody SysLoginForm form)throws IOException {
boolean captcha = sysCaptchaService.validate(form.getUuid(), form.getCaptcha());
if(! captcha){return R.error("Verification code is not correct");
}
// User information
SysUserEntity user = sysUserService.queryByUserName(form.getUsername());
// The account does not exist or the password is incorrect
if(user == null| |! user.getPassword().equals(new Sha256Hash(form.getPassword(), user.getSalt()).toHex())) {
return R.error("Incorrect account number or password");
}
// The account is locked
if(user.getStatus() == 0) {return R.error("Account has been locked. Please contact your administrator.");
}
// Generate tokens and save them to the database
R r = sysUserTokenService.createToken(user.getUserId());
return r;
}
/** * exit */
@PostMapping("/sys/logout")
public R logout(a) {
sysUserTokenService.logout(getUserId());
returnR.ok(); }}Copy the code
At this point we have completed the code for Shiro’s authentication process. Keep in mind that Shiro does not maintain users or permissions; These need to be designed/provided by ourselves; Then inject into Shiro through the appropriate interface. In the next article, I’ll cover implementing a complete permission control scheme using the RBAC permission model plus Shiro.
RBAC based on the authority model + Shiro + Springboot system login authority authentication module