Overview
As we all know, using JWT for permission verification. Compared with Session, the advantage of Session is that Session requires a large amount of server memory, and when multiple servers are used, it will involve shared Session issues, which is more troublesome when accessing mobile terminals such as mobile phones.
JWT does not need to be stored on the server and does not occupy server resources (that is, stateless). After the user logs in, he attaches to the token when accessing the request that requires permission (usually set in the Http request header). JWT does not have the problem of sharing multiple servers, nor does it have mobile access problems on mobile phones. If in order to improve security, the token can be bound to the user's IP address
Front-end process
User logged in through AJAX to get a token
After that, when accessing requires permission, attach a token to access.
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title> <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> <script type="application/javascript"> var header = ""; function login() { $.post("http://localhost:8080/auth/login", { username: $("#username").val(), password: $("#password").val() }, function (data) { console.log(data); header = data; }) } function toUserPageBtn() { $.ajax({ type: "get", url: "http://localhost:8080/userpage", beforeSend: function (request) { request.setRequestHeader("Authorization", header); }, success: function (data) { console.log(data); } }); } </script></head><body> <fieldset> <legend>Please Login</legend> <label>UserName</label><input type="text" id="username"> <label>Password</label><input type="text" id="password"> <input type="button" onclick="login()" value="Login"> </fieldset> <button id="toUserPageBtn" onclick="toUserPageBtn()">Access UserPage</button></body></html>Backend Process (Spring Boot + Spring Security + JJWT)
Ideas:
Write a user entity class and insert a piece of data
User (user) entity class
@Data@Entitypublic class User { @Id @GeneratedValue private int id; private String name; private String password; @ManyToMany(cascade = {CascadeType.REFRESH}, fetch = FetchType.EAGER) @JoinTable(name = "user_role", joinColumns = {@JoinColumn(name = "uid", referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "rid", referencedColumnName = "id")}) private List<Role> roles;} Role (Permission) Entity Class
@Data@Entitypublic class Role { @Id @GeneratedValue private int id; private String name; @ManyToMany(mappedBy = "roles") private List<User> users;} Insert data
User table
| id | name | password |
|---|---|---|
| 1 | linyuan | 123 |
Role Table
| id | name |
|---|---|
| 1 | USER |
User_ROLE table
| uid | rid |
|---|---|
| 1 | 1 |
Dao layer interface, obtains data through username, and returns an Optional object with a value of Java8
public interface UserRepository extends Repository<User,Integer> { Optional<User> findByName(String name);} Write LoginDTO for data transfer with the front end
@Datapublic class LoginDTO implements Serializable { @NotBlank(message = "The username cannot be empty") private String username; @NotBlank(message = "The password cannot be empty") private String password;} Write a token generation tool and create it using the JJWT library. There are three methods in total: generate a token (return a String), parse the token (return an Authentication authentication object), and verify the token (return a Boolean value)
@Componentpublic class JWTTokenUtils { private final Logger log = LoggerFactory.getLogger(JWTTokenUtils.class); private static final String AUTHORITIES_KEY = "auth"; private String secretKey; //Signing key private long tokenValidityInMilliseconds; //Expiration date private long tokenValidityInMillisecondsForRememberMe; //(Remember me)Expiration date @PostConstruct public void init() { this.secretKey = "Linyuanmima"; int secondIn1day = 1000 * 60 * 60 * 24; this.tokenValidityInMilliseconds = secondIn1day * 2L; this.tokenValidityInMillisecondsForRememberMe = secondIn1day * 7L; } private final static long EXPIRATIONTIME = 432_000_000; //Create Token public String createToken(Authentication authentication, Boolean rememberMe){ String authorities = authentication.getAuthorities().stream() //Get the user's permission string, such as USER, ADMIN .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); long now = (new Date()).getTime(); //Get the current timestamp Date validity; //Storage expiration time if (rememberMe){ validity = new Date(now + this.tokenValidityInMilliseconds); }else { validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe); } return Jwts.builder() //Create Token token.setSubject(authentication.getName()) //Set user-oriented.claim(AUTHORITIES_KEY,authorities) //Add permission attribute.setExpiration(validity) //Set invalidation time.signWith(SignatureAlgorithm.HS512,secretKey) //Generate signature.compact(); } //Get user permissions public Authentication getAuthentication(String token){ System.out.println("token:"+token); Claims claims = Jwts.parser() //Parse Token's payload .setSigningKey(secretKey) .parseClaimsJws(token) .getBody(); Collection<? extends GrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) //Get user permission string.map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); //Convert elements to GrantedAuthority interface collection User principal = new User(claims.getSubject(), "", authorities); return new UsernamePasswordAuthenticationToken(principal, "", authorities); } //Verify whether the Token is correct public boolean validateToken(String token){ try { Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); //Verify Token by key return true; }catch (SignatureException e) { //Signature exception log.info("Invalid JWT signature."); log.trace("Invalid JWT signature trace: {}", e); } catch (MalformedJwtException e) { //JWT format error log.info("Invalid JWT token."); log.trace("Invalid JWT token trace: {}", e); } catch (ExpiredJwtException e) { //JWT expired log.info("Expired JWT token."); log.trace("Expired JWT token trace: {}", e); } catch (UnsupportedJwtException e) { //The JWT log.info("Unsupported JWT token."); log.trace("Unsupported JWT token trace: {}", e); } catch (IllegalArgumentException e) { //The parameter error exception log.info("JWT token compact of handler are invalid."); log.trace("JWT token compact of handler are invalid trace: {}", e); } return false; }} Implement the UserDetails interface, representing the user entity class, is wrapped on our User object, contains permissions and other properties, and can be used by Spring Security
public class MyUserDetails implements UserDetails{ private User user; public MyUserDetails(User user) { this.user = user; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<Role> roles = user.getRoles(); List<GrantedAuthority> authorities = new ArrayList<>(); StringBuilder sb = new StringBuilder(); if (roles.size()>=1){ for (Role role : roles){ authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; } return Authorities; } return AuthorityUtils.commaSeparatedStringToAuthorityList(""); } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getName(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; }} Implement the UserDetailsService interface, which has only one method to obtain UserDetails. We can get the User object from the database, then wrap it into UserDetails and return it
@Servicepublic class MyUserDetailsService implements UserDetailsService { @Autowired UserRepository userRepository; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { //Load user object from the database Optional<User> user = userRepository.findByName(s); //For debugging, if the value exists, the user name and password are output. IfPresent((value)->System.out.println("Username:"+value.getName()+" User password: "+value.getPassword())); //If the value is no longer, return null return new MyUserDetails(user.orElse(null)); }} Write a filter. If the user carries the token, he will obtain the token, and generate an Authentication authentication object based on the token, and store it in the SecurityContext for permission control by Spring Security.
public class JwtAuthenticationTokenFilter extends GenericFilterBean { private final Logger log = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class); @Autowired private JWTTokenUtils tokenProvider; @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("JwtAuthenticationTokenFilter"); try { HttpServletRequest httpReq = (HttpServletRequest) servletRequest; String jwt = resolveToken(httpReq); if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) { //Verify whether the JWT is correct Authentication authentication = this.tokenProvider.getAuthentication(jwt); //Get user authentication information SecurityContextHolder.getContext().setAuthentication(authentication); //Save the user to SecurityContext } filterChain.doFilter(servletRequest, servletResponse); }catch (ExpiredJwtException e){ //JWT invalid log.info("Security exception for user {} - {}", e.getClaims().getSubject(), e.getMessage()); log.trace("Security exception trace: {}", e); ((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED); } } private String resolveToken(HttpServletRequest request){ String bearerToken = request.getHeader(WebSecurityConfig.AUTHORIZATION_HEADER); //Get TOKEN from the HTTP header if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")){ return bearerToken.substring(7, bearerToken.length()); //Return the Token string and remove Bearer } String jwt = request.getParameter(WebSecurityConfig.AUTHORIZATION_TOKEN); //Get TOKEN from the request parameters if (StringUtils.hasText(jwt)) { return jwt; } return null; }} Write a LoginController. The user accesses /auth/login through the user name and password, receives it through the LoginDTO object, and creates an Authentication object. The code is UsernamePasswordAuthenticationToken to determine whether the object exists. Verify the authentication object through the AuthenticationManager's authentication method. The AuthenticationManager implementation class ProviderManager will verify through the AuthenticationProvider (authentication processing). The default ProviderManager calls DaoAuthenticationProvider for authentication processing. The DaoAuthenticationProvider will obtain UserDetails through UserDetailsService (authentication information source) , if the authentication is successful, an Authention containing permissions is returned, and then set it to the SecurityContext through SecurityContextHolder.getContext().setAuthentication(), generate a token according to Authentication, and return it to the user.
@RestControllerpublic class LoginController { @Autowired private UserRepository userRepository; @Autowired private AuthenticationManager authenticationManager; @Autowired private JWTTokenUtils jwtTokenUtils; @RequestMapping(value = "/auth/login",method = RequestMethod.POST) public String login(@Valid LoginDTO loginDTO, HttpServletResponse httpResponse) throws Exception{ //Create an Authentication authentication object through the username and password, and implement the class as UsernamePasswordAuthenticationToken UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginDTO.getUsername(),loginDTO.getPassword()); //If the authentication object is not empty if (Objects.nonNull(authenticationToken)){ userRepository.findByName(authenticationToken.getPrincipal().toString()) .orElseThrow(()->new Exception("User does not exist")); } try { //Verify the Authentication object through the AuthenticationManager (default implemented as ProviderManager) authentication = authenticationManager.authenticate(authenticationToken); //Bind Authentication to SecurityContext SecurityContextHolder.getContext().setAuthentication(authentication); //Generate Token String token = jwtTokenUtils.createToken(authentication,false); //Write the Token to the Http header httpResponse.addHeader(WebSecurityConfig.AUTHORIZATION_HEADER,"Bearer "+token); return "Bearer "+token; }catch (BadCredentialsException authentication){ throw new Exception("Password Error"); } }} Write the Security configuration class, inherit the WebSecurityConfigurerAdapter, and override the configure method
@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String AUTHORIZATION_TOKEN = "access_token"; @Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth //Customize to get user information.userDetailsService(userDetailsService) //Set password encryption.passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { //Configure request access policy http //Close CSRF and CORS .cors().disable() .csrf().disable() //Secession is not required since token is used, no session is required .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() //Verify Http request.authorizeRequests() // Allow all users to access the homepage and log in.antMatchers("/","/auth/login").permitAll() //All other requests must be authenticated.anyRequest().authenticated() //User page requires user permission.antMatchers("/userpage").hasAnyRole("USER") .and() //Set logout().permitAll(); //Add JWT filter at http .addFilterBefore(genericFilterBean(), UsernamePasswordAuthenticationFilter.class); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public GenericFilterBean genericFilterBean genericFilterBean() { return new JwtAuthenticationTokenFilter(); }} Write a Controller for Testing
@RestControllerpublic class UserController { @PostMapping("/login") public String login() { return "login"; } @GetMapping("/") public String index() { return "hello"; } @GetMapping("/userpage") public String httpApi() { System.out.println(SecurityContextHolder.getContext().getAuthentication().getPrincipal()); return "userpage"; } @GetMapping("/adminpage") public String httpSuite() { return "userpage"; }}Case source code download (local download)
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.