咱們在Spring Initializr中初始化java
勾選Spring Web和Spring Securitylinux
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.ssrmj</groupId> <artifactId>login-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>login-demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- spring-security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- redis 操做依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- jwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.6.0</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
spring: mvc: throw-exception-if-no-handler-found: true resources: add-mappings: false ###Redis redis: host: linux的ip port: 6379 timeout: 2000ms password: redis密碼 #密碼 jedis: pool: max-active: 10 max-idle: 8 min-idle: 2 max-wait: 1000ms logging: level: org.springframework.security: info root: info path: e:/log/login-demo-log ### jwt jwt: ###過時時間 單位s time: 1800 ###安全密鑰 secret: "BlogSecret" ###token前綴 prefix: "Bearer " ###http頭key header: "Authorization"
注:setter、getter和toString採用lombok
entity.Result(返回結果實體類)web
package com.ssrmj.model.entity; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; import lombok.ToString; /** * @Description: 返回結果實體類 * @Author: Mt.Li */ @JsonInclude(JsonInclude.Include.NON_NULL) @Data @ToString public class Result { private Integer code; // 返回狀態碼 private String message; // 返回信息 private Object data; // 返回數據 private Result(){ } public Result(Integer code, String message) { super(); this.code = code; this.message = message; } public Result(Integer code, String message, Object data) { super(); this.code = code; this.message = message; this.data = data; } public static Result create(Integer code, String message){ return new Result(code,message); } public static Result create(Integer code, String message, Object data){ return new Result(code,message,data); } }
entity.StatusCode(自定義狀態碼)redis
package com.ssrmj.model.entity; /** * 自定義狀態碼 */ public class StatusCode { // 操做成功 public static final int OK = 200; // 失敗 public static final int ERROR = 201; // 用戶名或密碼錯誤 public static final int LOGINERROR = 202; // token過時 public static final int TOKENEXPIREE = 203; // 權限不足 public static final int ACCESSERROR = 403; // 遠程調用失敗 public static final int REMOTEERROR = 204; // 重複操做 public static final int REPERROR = 205; // 業務層錯誤 public static final int SERVICEERROR = 500; // 資源不存在 public static final int NOTFOUND = 404; }
pojo.Role(角色)spring
package com.ssrmj.model.pojo; import lombok.Data; import lombok.ToString; /** * @Description: 角色 * @Author: Mt.Li */ @Data @ToString public class Role { private Integer id;//角色id private String name;//角色名 }
pojo.User(用戶)docker
package com.ssrmj.model.pojo; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import lombok.ToString; import java.io.Serializable; import java.util.List; /** * @Description: 用戶 * @Author: Mt.Li */ @Data @ToString public class User implements Serializable { // 自動生成的serialVersionUID private static final long serialVersionUID = 7015283901517310682L; private Integer id; private String name; private String password; // 用戶狀態,0-封禁,1-正常 private Integer state; @JsonIgnore private List<Role> roles; }
注:代碼中自動生成的serialVersionUID數據庫
一、BeanConfig(將一些不方便加@Component註解的類放在此處)
什麼意思呢,就是有的類咱們用@Autowired注入的時候,spring不能識別,因而在這裏寫成方法注入容器apache
package com.ssrmj.config; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Component; /** * 將一些不方便加@Component註解的類放在此處加入spring容器 */ @Component public class BeanConfig { /** * spring-security加密方法 */ @Bean public BCryptPasswordEncoder encoder() { return new BCryptPasswordEncoder(); } /** * spring-boot內置的json工具 */ @Bean public ObjectMapper objectMapper() { return new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL); } }
二、JwtConfig(Jwt配置類,將yml中的配置引入)json
package com.ssrmj.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @ConfigurationProperties(prefix = "jwt") @Component public class JwtConfig { public static final String REDIS_TOKEN_KEY_PREFIX = "TOKEN_"; private long time; // 過時時間 private String secret; // JWT密碼 private String prefix; // Token前綴 private String header; // 存放Token的Header Key public long getTime() { return time; } public void setTime(long time) { this.time = time; } public String getSecret() { return secret; } public void setSecret(String secret) { this.secret = secret; } public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getHeader() { return header; } public void setHeader(String header) { this.header = header; } }
三、WebSecurityConfig(Security攔截配置)ubuntu
package com.ssrmj.config; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; /** * @Description: * @Author: Mt.Li */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) // 開啓Spring方法級安全,開啓前置註解,一樣也是開啓了Security註解模式 public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity httpSecurity) throws Exception { //禁用csrf //options所有放行 //post 放行 httpSecurity.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() .antMatchers(HttpMethod.POST).permitAll() // 爲了方便測試,放行post .antMatchers(HttpMethod.PUT).authenticated() .antMatchers(HttpMethod.DELETE).authenticated() .antMatchers(HttpMethod.GET).authenticated(); httpSecurity.headers().cacheControl(); } }
JwtTokenUtil(關於token操做的工具類)
package com.ssrmj.util; import com.ssrmj.config.JwtConfig; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.io.Serializable; import java.util.*; @Component public class JwtTokenUtil implements Serializable { private static final long serialVersionUID = 7965205899118624911L; private static final String CLAIM_KEY_USERNAME = "sub"; private static final String CLAIM_KEY_CREATED = "created"; private static final String CLAIM_KEY_ROLES = "roles"; @Autowired private JwtConfig jwtConfig; public Date getCreatedDateFromToken(String token) { Date created; try { final Claims claims = getClaimsFromToken(token); created = new Date((Long)claims.get(CLAIM_KEY_CREATED)); } catch (Exception e) { created = null; } return created; } /** * 從token中獲取過時時間 */ public Date getExpirationDateFromToken(String token) { Date expiration; try { final Claims claims = getClaimsFromToken(token); expiration = claims.getExpiration(); } catch (Exception e) { expiration = null; } return expiration; } private Claims getClaimsFromToken(String token) { Claims claims; try { claims = Jwts.parser() .setSigningKey(jwtConfig.getSecret()) .parseClaimsJws(token) .getBody(); } catch (Exception e) { claims = null; } return claims; } /** * 生成過時時間 單位[ms] * */ private Date generateExpirationDate() { // 當前毫秒級時間 + yml中的time * 1000 return new Date(System.currentTimeMillis() + jwtConfig.getTime() * 1000); } /** * 根據提供的用戶詳細信息生成token */ public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(3); claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername()); // 放入用戶名 claims.put(CLAIM_KEY_CREATED, new Date()); // 放入token生成時間 List<String> roles = new ArrayList<>(); Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities(); for (GrantedAuthority authority : authorities) { // SimpleGrantedAuthority是GrantedAuthority實現類 // GrantedAuthority包含類型爲String的獲取權限的getAuthority()方法 // 提取角色並放入List中 roles.add(authority.getAuthority()); } claims.put(CLAIM_KEY_ROLES, roles); // 放入用戶權限 return generateToken(claims); } /** * 生成token(JWT令牌) */ private String generateToken(Map<String, Object> claims) { return Jwts.builder() .setClaims(claims) .setExpiration(generateExpirationDate()) .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret()) .compact(); } }
結構圖:
RoleDao
package com.ssrmj.dao; import com.ssrmj.model.pojo.Role; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface RoleDao { /** * 根據用戶id查詢角色 */ List<Role> findUserRoles(Integer id); }
UserDao
package com.ssrmj.dao; import com.ssrmj.model.pojo.User; import org.springframework.stereotype.Repository; @Repository public interface UserDao { /** * 根據用戶名查詢用戶 */ User findUserByName(String name); }
RoleDaoImpl
package com.ssrmj.dao.impl; import com.ssrmj.dao.RoleDao; import com.ssrmj.model.pojo.Role; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * @Description: * @Author: Mt.Li */ @Service public class RoleDaoImpl implements RoleDao { private List<Role> roles = new ArrayList<>(); private static Role r1 = new Role(); private static Role r2 = new Role(); @Override public List<Role> findUserRoles(Integer id) { if(id == 1) { r1.setId(0); r1.setName("ADMIN"); r2.setId(1); r2.setName("USER"); roles.add(r1); roles.add(r2); return roles; } return null; } }
UserDaoImpl
package com.ssrmj.dao.impl; import com.ssrmj.dao.UserDao; import com.ssrmj.model.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @Description: * @Author: Mt.Li */ @Service public class UserDaoImpl implements UserDao { @Autowired RoleDaoImpl roleDaoImpl; @Override public User findUserByName(String name) { User user = new User(); user.setId(1); user.setName("admin"); user.setPassword("123456"); user.setState(1); user.setRoles(roleDaoImpl.findUserRoles(user.getId())); return user; } }
LoginService
package com.ssrmj.service; import com.ssrmj.config.JwtConfig; import com.ssrmj.dao.impl.RoleDaoImpl; import com.ssrmj.dao.impl.UserDaoImpl; import com.ssrmj.model.pojo.Role; import com.ssrmj.model.pojo.User; import com.ssrmj.util.JwtTokenUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.*; import java.util.concurrent.TimeUnit; /** * @Description: * @Author: Mt.Li */ @Service public class LoginService implements UserDetailsService { @Autowired UserDaoImpl userDao; @Autowired RoleDaoImpl roleDao; @Autowired private RedisTemplate<String, String> redisTemplate; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private JwtConfig jwtConfig; public Map login(User user) throws RuntimeException{ User dbUser = this.findUserByName(user.getName()); // 用戶不存在 或者 密碼錯誤 if (dbUser == null || !dbUser.getName().equals("admin") || !dbUser.getPassword().equals("123456")) { throw new UsernameNotFoundException("用戶名或密碼錯誤"); } // 用戶已被封禁 if (0 == dbUser.getState()) { throw new RuntimeException("你已被封禁"); } // 用戶名 密碼匹配,獲取用戶詳細信息(包含角色Role) final UserDetails userDetails = this.loadUserByUsername(user.getName()); // 根據用戶詳細信息生成token final String token = jwtTokenUtil.generateToken(userDetails); Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities(); List<String> roles = new ArrayList<>(); for (GrantedAuthority authority : authorities) { // SimpleGrantedAuthority是GrantedAuthority實現類 // GrantedAuthority包含類型爲String的獲取權限的getAuthority()方法 // 提取角色並放入List中 roles.add(authority.getAuthority()); } Map<String, Object> map = new HashMap<>(3); map.put("token", jwtConfig.getPrefix() + token); map.put("name", user.getName()); map.put("roles", roles); //將token存入redis(TOKEN_username, Bearer + token, jwt存放五天 過時時間) jwtConfig.time 單位[s] redisTemplate.opsForValue(). set(JwtConfig.REDIS_TOKEN_KEY_PREFIX + user.getName(), jwtConfig.getPrefix() + token, jwtConfig.getTime(), TimeUnit.SECONDS); return map; } /** * 根據用戶名查詢用戶 */ public User findUserByName(String name) { return userDao.findUserByName(name); } /** * 根據用戶名查詢用戶 */ @Override public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException { User user = userDao.findUserByName(name); // 新建權限集合,SimpleGrantedAuthority是GrantedAuthority實現類 List<SimpleGrantedAuthority> authorities = new ArrayList<>(1); //用於添加用戶的權限。將用戶權限添加到authorities List<Role> roles = roleDao.findUserRoles(user.getId()); // 查詢該用戶的角色 for (Role role : roles) { // 將role的name放入權限的集合 authorities.add(new SimpleGrantedAuthority(role.getName())); } return new org.springframework.security.core.userdetails.User(user.getName(), "***********", authorities); } }
UserController
package com.ssrmj.controller; import com.ssrmj.model.entity.Result; import com.ssrmj.model.entity.StatusCode; import com.ssrmj.model.pojo.User; import com.ssrmj.service.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; /** * @Description: * @Author: Mt.Li */ @RestController @RequestMapping("/user") public class UserController { @Autowired private LoginService loginService; /** * 登陸返回token */ @PostMapping("/login") public Result login(User user) { try { Map map = loginService.login(user); return Result.create(StatusCode.OK, "登陸成功", map); } catch (UsernameNotFoundException e) { return Result.create(StatusCode.LOGINERROR, "登陸失敗,用戶名或密碼錯誤"); } catch (RuntimeException re) { return Result.create(StatusCode.LOGINERROR, re.getMessage()); } } }
測試咱們用postman模擬請求
點擊Send,獲得響應以下
咱們利用Redis Desktop Manager查看redis數據庫的狀況
因爲redis是基於內存的數據庫,存取速度很快,而且有可持久化的特性,用來存儲token再合適不過了。
注:博主才疏學淺,若有錯誤,請及時說明,謝謝。