Achieve the goal
- After login, users can use tokens to access some interfaces that require login.
What are JWT and Shiro
JWT
JWT(JSON Web Tokens) is a JSON-based open standard (RFC 7519), cross-domain authentication solution.
composition
The JSON Web Token consists of three parts separated by dots (.). The connection. The three parts are:
- Header
- Payload
- Signature
Header
The header typically consists of two parts: the token type (” JWT “) and the algorithm name (such as HMAC SHA256 or RSA, etc.).
Such as:
{
"typ": "JWT"."alg": "HS256"
}
Copy the code
Then, in the JSON using Base64 code is the first part of the get JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload
Payload is the specific content of the Token. Some of these things are standard fields, and you can add other things as you want. Here are the standard fields:
- Iss: Issuer
- Sub: Subject
- Aud: Audience
- Exp: Expiration time
- Diagindex.nbf: Not before
- Iat: Issued at
- Jti: JWT ID Payload, which uses the iss issuer, and exp expiration time. There are also two custom fields, one for name and one for admin.
{
"iss": "ninghao.net"."exp": "1438955445"."name": "wanghao"."admin": true
}
Copy the code
Encoding this JSON using Base64 results in the second part of the JWT
eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ Signature
Signature
The last part of the JWT is the Signature, which consists of three parts. It is encoded with Base64 encoding header.payload and encrypted with an encryption algorithm. The encryption process is as follows:
String encodedString = base64UrlEncode(header) + “.” + base64UrlEncode(payload);
String token = HMACSHA256(encodedString, ‘secret’);
This is where the third part of the token comes in:
SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
And then finally let’s use these three parts. Concatenation is our Token to send to the client:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWl uIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
Shiro
Shiro is a security framework that is mainly used for authentication and authorization. In this case, we use authorization, which intercepts urls to determine whether users have permission to access them.
Overall implementation idea
The server generates the token through JWT and sends it to the client. After receiving the request, Shiro will put the token in the request header every time. Shiro will intercept every interface and then take out the token in the request header and give it to JWT for verification. If the verification passes, the client will be released.
Code implementation
Create a Spring Boot project with the following pom.xml file:
<? xml version="1.0" encoding="UTF-8"? > <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> < modelVersion > 4.0.0 < / modelVersion > < the parent > < groupId > org. Springframework. Boot < / groupId > The < artifactId > spring - the boot - starter - parent < / artifactId > < version > 2.1.7. RELEASE < / version > < relativePath / > <! -- lookup parent from repository --> </parent> <groupId>com.domain</groupId> <artifactId>hello-jwt-shiro</artifactId> < version > 0.0.1 - the SNAPSHOT < / version > < name > hello - JWT - shiro < / name > < description > Demo projectforSpring Boot with JWT and Shrio</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> < project. Reporting. OutputEncoding > utf-8 < / project. Reporting. OutputEncoding > < Java version > 1.8 < / Java version > The < shiro. Spring. Version > 1.4.0 < / shiro. Spring. Version > < JWT. Auth0. Version > 3.2.0 < / JWT. Auth0. Version > < / properties > <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <! -- Use Redis for data caching, if you don't need to rely on --> <! --<dependency>--> <! --<groupId>org.springframework.boot</groupId>--> <! --<artifactId>spring-boot-starter-data-redis</artifactId>--> <! --</dependency>--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>${shiro.spring.version}</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${jwt.auth0.version}</version> </dependency> <! --<dependency>--> <! --<groupId>org.apache.httpcomponents</groupId>--> <! --<artifactId>httpclient</artifactId>--> <! - < version > 4.5.5 < / version > -- > <! --</dependency>--> <! --<dependency>--> <! --<groupId>org.apache.commons</groupId>--> <! --<artifactId>commons-lang3</artifactId>--> <! - < version > 3.7 < / version > -- > <! --</dependency>--> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>Copy the code
Shiro configuration file
Set the Token filter and the URL to filter. All urls that need to be filtered will go through the filter first, and only after the filter is verified, will they reach the controller.
@Configuration
public class ShiroConfiguration {
@Bean("securityManager") public DefaultWebSecurityManager getManager(MyRealm realm) { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); // Use your own realm Manager.setrealm (realm); / * * close shiro's own session, see document * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
manager.setSubjectDAO(subjectDAO);
return manager;
}
@Bean("shiroFilterFactoryBean") public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); // Add your own Filter and name it JWT Map<String, Filter> filterMap = new HashMap<>(); filterMap.put("token", new TokenAccessFilter());
factoryBean.setFilters(filterMap);
factoryBean.setSecurityManager(securityManager);
factoryBean.setUnauthorizedUrl("/ 401"); / * * * http://shiro.apache.org/web.html custom url rules#urls-*/ Map<String, String> filterRuleMap = new HashMap<>(); // All requests pass through our own JWT Filter filterRulemap.put ("/login"."anon");
filterRuleMap.put("/ * *"."token"); // Access 401 and 404 pages without passing our Filter filterrulemap.put ("/ 401"."anon");
factoryBean.setFilterChainDefinitionMap(filterRuleMap);
returnfactoryBean; } /** * add annotation support */ @bean @dependson ()"lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // Enforce cglib, Prevent repeated agents and agents may cause error problems / / https://zhuanlan.zhihu.com/p/29161098 defaultAdvisorAutoProxyCreator setProxyTargetClass (true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
returnadvisor; }}Copy the code
Filter implementation
Obtain the token in the client request header and submit it to JWT for verification and comparison. If the token passes the request, it will be released. After the pass, the Controller code will be executed.
public class TokenAccessFilter extends AccessControlFilter {
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
if(((HttpServletRequest) servletRequest).getMethod().equalsIgnoreCase("OPTIONS")) {
return true;
}
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws BusinessException, IOException {
String token = ((HttpServletRequest) servletRequest).getHeader("token");
if (token==null || token.length()==0) {
responseError(servletResponse,401,"Not logged in yet");
return false;
}
String username = JWTUtil.getUsername(token);
if(! JWTUtil.verify(token,username)) { responseError(servletResponse,401,"Token verification failed");
return false;
}
return true; } private void responseError(ServletResponse response,int code,String errorMsg) throws IOException { HttpServletResponse httpServletResponse = (HttpServletResponse) response; httpServletResponse.setHeader("Access-Control-Allow-Origin"."*");
httpServletResponse.setHeader("Access-Control-Allow-Credentials"."true");
httpServletResponse.setHeader("Access-Control-Allow-Methods"."*"); httpServletResponse.setHeader("Access-Control-Allow-Headers"."*");
httpServletResponse.setContentType("application/json; charset=UTF-8");
ResponseBean baseResponse = new ResponseBean(code,errorMsg,null);
OutputStream os = httpServletResponse.getOutputStream();
os.write(new ObjectMapper().writeValueAsString(baseResponse).getBytes("UTF-8")); os.flush(); os.close(); }}Copy the code
Token generation and verification
Public class JWTUtil {private static final Long EXPIRE_TIME = 5*60*1000; private static final String SECRET ="XX#$%()(#*! ()! KL<>
? N<:{LWPW"
; /** * Verify whether the token is correct * @param Token key * @param secret User password * @return*/ public static Boolean verify(String token, String username) { try { Algorithm algorithm = Algorithm.HMAC256(SECRET); JWTVerifier verifier = JWT.require(algorithm) .withClaim("username", username)
.build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false; }} /** * Can obtain the information in token without secret decryptionreturnPublic static String getUsername(String token) {try {DecodedJWT JWT = jwt. decode(token);return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
returnnull; }} /** * the signature is generated and expires in 5 minutes. * @param username username * @param secret user password * @returnToken */ public static String sign(String username) {try {Date Date = new Date(System.currentTimeMillis()+EXPIRE_TIME); Algorithm algorithm = Algorithm.HMAC256(SECRET); // With username informationreturn JWT.create()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
} catch (UnsupportedEncodingException e) {
returnnull; }}}Copy the code
Custom Realm
Custom permission authentication and identity authentication logic.
The doGetAuthorizationInfo method is permission authentication. The doGetAuthenticationInfo method is authentication
GetSubject (Request, Response).login(token) is handled by this custom class.
@Service
public class MyRealm extends AuthorizingRealm {
private static final Logger LOGGER = LogManager.getLogger(MyRealm.class);
private UserService userService;
@Autowired
public void setUserService(UserService userService) { this.userService = userService; } / / / / / * * * hole! // */ / public Boolean supports(AuthenticationToken token) {//returntoken instanceof JWTToken; / /} / / / / / * * * only when the user permission to check this method is called, for example checkRole, such / / * / @ checkPermission Override protected AuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principals) {
String username = JWTUtil.getUsername(principals.toString());
UserBean user = userService.getUser(username);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole(user.getRole());
Set<String> permission = new HashSet<>(Arrays.asList(user.getPermission().split(",")));
simpleAuthorizationInfo.addStringPermissions(permission);
returnsimpleAuthorizationInfo; } /** * By default, this method is used to verify whether the user name is correct. */ @Override protected AuthenticationInfodoGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { String token = (String) auth.getCredentials(); String username = jwtutil.getUsername (token); String username = jwtutil.getUsername (token);if (username == null) {
throw new AuthenticationException("token invalid");
}
UserBean userBean = userService.getUser(username);
if (userBean == null) {
throw new AuthenticationException("User didn't existed!");
}
if (! JWTUtil.verify(token, username)) {
throw new AuthenticationException("Username or password error");
}
return new SimpleAuthenticationInfo(token, token, "my_realm"); }}Copy the code
Complete project code
Github.com/domain9065/…