I. Scene and environment
1. I’m a white web worker who worries about my future every day. Record the daily development process for the first time, if there is any improper expression, please laugh it off; 2. The front-end development environment of this example uses Angular framework, and the back-end uses Springboot framework; 3. The objectives are as follows: a. The front end realizes login operation (without registration function); B. After receiving the login information, the back-end generates the expiration token (a section of secret key generated by the back-end algorithm) and returns it to the front-end as the result; C. Each subsequent request of the front end will carry the token to verify with the back end; D. The front-end response will be successful within the token validity time, and the back-end will update the token validity time in real time (no implementation yet). If the token is invalid, the login page will be returned.
Second, back-end implementation logic
Note: The whole server side project structure is as follows (login token interception is only a part of this project, the project address will be affixed at the end of the article) :
1. Add AccessToken class model
Add accesstoken. Java to model file. This model class holds verification token information:
/** * @param access_token Token field; * @param token_type Token type field; * @param expires_in Token validity period field; */ public class AccessToken { private String access_token; private String token_type; private long expires_in; public StringgetAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public String getToken_type() {
return token_type;
}
public void setToken_type(String token_type) {
this.token_type = token_type;
}
public long getExpires_in() {
return expires_in;
}
public void setExpires_in(long expires_in) { this.expires_in = expires_in; }}Copy the code
2. Add Audience model
@ConfigurationProperties(prefix = "audience")
public class Audience {
private String clientId;
private String base64Secret;
private String name;
private int expiresSecond;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getBase64Secret() {
return base64Secret;
}
public void setBase64Secret(String base64Secret) {
this.base64Secret = base64Secret;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getExpiresSecond() {
return expiresSecond;
}
public void setExpiresSecond(int expiresSecond) { this.expiresSecond = expiresSecond; }}Copy the code
@configurationProperties (prefix = “audience”) Gets the configuration file information (application.properties) as follows:
server.port=8888
spring.profiles.active=dev
server.servlet.context-path=/movies
audience.clientId=098f6bcd4621d373cade4e832627b4f6
audience.base64Secret=MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY=
audience.name=xxx
audience.expiresSecond=1800
Copy the code
The configuration file defines the port number, root path and information about the fields related to the audience (also named according to online information). The function of the audience is mainly to generate valid tokens at the first login and store the token information into the above AccessToken model. After a successful login, you can verify the token information carried by the front-end.
Create CreateTokenUtils utility class as JWT package
XML file (this is similar to the NPM package installed in package.json)
<dependency> <groupId> IO. Jsonwebtoken </groupId> <artifactId> JJWT </artifactId> <version>0.6.0</version> </dependency>Copy the code
Create tool class CreateTokenUtils under uITls folder
public class CreateTokenUtils {
private static Logger logger = LoggerFactory.getLogger(CreateTokenUtils.class);
/**
*
* @param request
* @return s;
* @throws Exception
*/
public static ReturnModel checkJWT(HttpServletRequest request,String base64Secret)throws Exception{
Boolean b = null;
String auth = request.getHeader("Authorization");
if((auth ! = null) && (auth.length() > 4)){String HeadStr = auth.substring(0,3).tolowercase ();if(HeadStr.compareTo("mso") == 0){
auth = auth.substring(4,auth.length());
logger.info("claims:"+parseJWT(auth,base64Secret));
Claims claims = parseJWT(auth,base64Secret);
b = claims==null?false:true; }}if(b == false){
logger.error("getUserInfoByRequest:"+ auth);
return new ReturnModel(-1,b);
}
return new ReturnModel(0,b);
}
public static Claims parseJWT(String jsonWebToken, String base64Security){
try
{
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))
.parseClaimsJws(jsonWebToken).getBody();
return claims;
}
catch(Exception ex)
{
return null;
}
}
public static String createJWT(String name,String audience, String issuer, long TTLMillis, String base64Security)
{
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(base64Security);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
JwtBuilder builder = Jwts.builder().setHeaderParam("typ"."JWT")
.claim("unique_name", name)
.setIssuer(issuer)
.setAudience(audience)
.signWith(signatureAlgorithm, signingKey);
if (TTLMillis >= 0) {
long expMillis = nowMillis + TTLMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp).setNotBefore(now);
}
returnbuilder.compact(); }}Copy the code
This utility class has three static methods: CreateJWT — This method is called in the logon interface. ParseJWT — this method is called in the checkJWT, parseJWT, parseJWT, parseJWT Splitting JWT token values into audience modules allows you to view Claims objects at break points in the parseJWT method and find that the values stored in the fields correspond to the values of the Audience object. Audience. ExpiresSecond =1800 The Claims object will parse the tokens’ validity period and determine whether the tokens have expired
4, the realization of the interceptor HTTPBasicAuthorizeHandler class implementation
In typesHandlers folder HTTPBasicAuthorizeHandler new class, the code is as follows:
@WebFilter(filterName = "basicFilter",urlPatterns = "/ *")
public class HTTPBasicAuthorizeHandler implements Filter {
private static Logger logger = LoggerFactory.getLogger(HTTPBasicAuthorizeHandler.class);
private static final Set<String> ALLOWED_PATHS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("/person/exsit")));
@Autowired
private Audience audience;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("filter is init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.info("filter is start");
try {
logger.info("audience:"+audience.getBase64Secret());
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String path = request.getRequestURI().substring(request.getContextPath().length()).replaceAll("[/] + $"."");
logger.info("url:"+path);
Boolean allowedPath = ALLOWED_PATHS.contains(path);
if(allowedPath){
filterChain.doFilter(servletRequest,servletResponse);
}else {
ReturnModel returnModel = CreateTokenUtils.checkJWT((HttpServletRequest)servletRequest,audience.getBase64Secret());
if(returnModel.getCode() == 0){
filterChain.doFilter(servletRequest,servletResponse);
}else {
// response.setCharacterEncoding("UTF-8");
// response.setContentType("application/json; charset=utf-8");
// response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// ReturnModel rm = new ReturnModel();
// response.getWriter().print(rm);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void destroy() {
logger.info("filter is destroy"); }}Copy the code
Init, doFitler, deStory, doFitler, doFitler, deStory, doFitler
A, the first login request cannot be blocked because it does not have a token value.
private static final Set<String> ALLOWED_PATHS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("/person/exsit")));
Copy the code
Here, my login interface path is “/person/exsit”, so I split the front-end request path:
String path = request.getRequestURI().substring(request.getContextPath().length()).replaceAll("[/] + $"."");
Copy the code
The two are compared as follows:
Boolean allowedPath = ALLOWED_PATHS.contains(path);
Copy the code
According to the value of allowedPath to determine whether to intercept; B. Call the checkJWT method of the above tool class during interception to determine whether the token is valid:
ReturnModel returnModel = CreateTokenUtils.checkJWT((HttpServletRequest)servletRequest,audience.getBase64Secret());
Copy the code
ReturnModel is the return type structure I defined, under the Model file; C, if the token is invalid, the processing code is commented:
The front-end Angular interceptor conflicts with the backend, causing the front-end code to fail, as explained later. D. There are two ways to configure interceptors (only one of which is described here) :
Note: The filtered paths don’t include the root paths of configuration files, such as the front-end access interface path “/movies/people/exist”, where movies are root paths configured in the configuration file. If you want to intercept those paths, you can just use urlPatterns= “/people/exist”.
5. Implementation of login class
Create a New PersonController class in the Controller folder as follows
/**
* Created by jdj on 2018/4/23.
*/
@RestController
@RequestMapping("/person") public class PersonController { private final static Logger logger = LoggerFactory.getLogger(PersonController.class); @Autowired private PersonBll personBll; @Autowired private Audience audience; ** * @content: person * @param id=1; * @return returnModel
*/
@RequestMapping(value = "/exsit",method = RequestMethod.POST)
public ReturnModel exsit(
@RequestParam(value = "userName") String userName,
@RequestParam(value = "passWord") String passWord
){
String md5PassWord = Md5Utils.getMD5(passWord);
String id = personBll.getPersonExist(userName,md5PassWord);
if(id == null||id.length()<0){
return new ReturnModel(-1,null);
}else {
Map<String,Object> map = new HashMap<>();
Person person = personBll.getPerson(id);
map.put("person",person);
String accessToken = CreateTokenUtils
.createJWT(userName,audience.getClientId(), audience.getName(),audience.getExpiresSecond() * 1000, audience.getBase64Secret());
AccessToken accessTokenEntity = new AccessToken();
accessTokenEntity.setAccess_token(accessToken);
accessTokenEntity.setExpires_in(audience.getExpiresSecond());
accessTokenEntity.setToken_type("bearer");
map.put("accessToken",accessTokenEntity);
return new ReturnModel(0,map);
}
}
/**
* @content:list
* @param null;
* @return returnModel
*/
@RequestMapping(value = "/list",method = RequestMethod.GET)
public ReturnModel list(){
List<Person> list = personBll.selectAll();
if(list.size()==0){
return new ReturnModel(-1,null);
}else {
return new ReturnModel(0,list);
}
}
@RequestMapping(value = "/item",method = RequestMethod.GET)
public ReturnModel getItem(
@RequestParam(value = "id") String id
){
Person person = personBll.getPerson(id);
if(person ! = null){return new ReturnModel(0,person);
}else {
return new ReturnModel(-1,"No such user"); }}}Copy the code
The front end calls the interface path of this class: “/movies/people/exist.” First it queries the database
String id = personBll.getPersonExist(userName,md5PassWord);
Copy the code
If the query exists, create accessToken
String accessToken = CreateTokenUtils
.createJWT(userName,audience.getClientId(), audience.getName(),audience.getExpiresSecond() * 1000, audience.getBase64Secret());
Copy the code
Finally, the integration returns to the front-end model
AccessToken accessTokenEntity = new AccessToken();
accessTokenEntity.setAccess_token(accessToken);
accessTokenEntity.setExpires_in(audience.getExpiresSecond());
accessTokenEntity.setToken_type("bearer");
map.put("accessToken",accessTokenEntity);
return new ReturnModel(0,map);
Copy the code
The Controller class also has two interfaces that the front end calls after successful login.
The above is the service end of the implementation logic, the next description of the front-end implementation logic, I am a front-end small code farmers, back-end is mostly not, if there is an error, please laugh ha ~_~ ha
Three, front-end implementation logic
The front end uses the Angular framework, as shown below
1. Front-end HTTP requests (specifically httpClient requests)
All requests are in the service folder service.service.ts file with the following code:
import { Injectable } from '@angular/core';
import { HttpClient,HttpHeaders } from "@angular/common/http";
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/forkJoin';
@Injectable()
export class ServiceService {
movies:string;
httpOptions:Object;
constructor(public http:HttpClient) {
this.movies = "/movies";
this.httpOptions = {
headers:new HttpHeaders({
'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',}),}} /** Login module start */ loginMovies(body){const url = this.movies+"/person/exsit";
const param = 'userName='+body.userName+"&passWord="+body.password;
returnthis.http.post(url,param,this.httpOptions); } /** Login module end */ // home; getPersonItem(param){ const url = this.movies+"/person/item";
returnthis.http.get(url,{params:param}); } // Personal centergetPersonList(){
const url = this.movies+"/person/list";
returnthis.http.get(url); /** end */}Copy the code
HttpOptions: httpOptions (httpOptions) : httpOptions (httpOptions) : httpOptions (httpOptions) : httpOptions (httpOptions) This is then added to app.modules.ts to provide, called dependency injection, so that the servcie method can be called on individual pages
providers: [ServiceService,httpInterceptorProviders]
Copy the code
HttpInterceptorProviders is a front-end interceptor. Every time the front-end interceptor requests a result, either a success or an error occurs.
2. Implementation of front-end interceptor
Create a new interceptorService. ts file in the app file with the following code:
import { Injectable } from '@angular/core';
import { HttpEvent,HttpInterceptor,HttpHandler,HttpRequest,HttpResponse} from "@angular/common/http";
import {Observable} from "rxjs/Observable";
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
import { mergeMap } from 'rxjs/operators';
import {Router} from '@angular/router';
@Injectable()
export class InterceptorService implements HttpInterceptor{
constructor(
private router:Router,
){ }
authorization:string = "";
authReq:any;
intercept(req:HttpRequest<any>,next:HttpHandler):Observable<HttpEvent<any>>{
this.authorization = "mso " + localStorage.getItem("accessToken");
if (req.url.indexOf('/person/exsit') === -1) {
this.authReq = req.clone({
url:req.url,
headers:req.headers.set("Authorization",this.authorization)
});
}else{
this.authReq = req.clone({
url:req.url,
});
}
return next.handle(this.authReq).pipe(mergeMap((event:any) => {
if(event instanceof HttpResponse && event.body === null){
return this.handleData(event);
}
returnObservable.create(observer => observer.next(event)); })); } private handleData(event: HttpResponse<any>): Observable<any> {switch (event.status) {case 200:
if (event instanceof HttpResponse) {
const body: any = event.body;
if(body === null) { this.backForLoginOut(); }}break;
case401: // Not logged in status code this.backForLoginout ();break;
case 404:
case 500:
break;
default:
return ErrorObservable.create(event);
}
}
private backForLoginOut() {if(localStorage.getItem("accessToken") !== null || localStorage.getItem("person")! == null){localStorage.removeItem("accessToken");
localStorage.removeItem("person");
}
if(localStorage.getItem("accessToken") === null && localStorage.getItem("person") === null){
this.router.navigateByUrl('/login'); }}}Copy the code
The implementation of interceptors is explained in detail on the official website, but interceptors have a few holes: A. If you are using Angular2 and you request the import {Http} from “@angular/ Http “package Http, the interceptor is invalid and you may need to write it differently. Import {HttpClient,HttpHeaders} from “@angular/common/ HTTP “; import {HttpClient,HttpHeaders} from “@angular/common/ HTTP “; B. Among the methods returned by interceptors:
return next.handle(this.authReq).pipe(mergeMap((event:any) => {
if(event instanceof HttpResponse && event.body === null){
return this.handleData(event);
}
return Observable.create(observer => observer.next(event));
}));
Copy the code
The first event:{type:0} will return the object for the second time, as shown in the screenshot below: First time
3. Login effect
The above logic is the implementation process, the following is the overall effect: in the login logic, I use localStorage to store the token value:
The above is about the separated login verification at the front and back ends. There is still a step to complete, which is the validity period of token update, which can be supplemented at some time. The above code uses idea editor at the back end, and the back-end service construction will involve many configurations. The code github address is as follows: github.com/yuelinghuny… Please give me a thumbs up. It is the first time to write a record document. I will keep writing and believe that it will get better and better.