Springboot+SpringSecurity+JWT实现⽤户登录和权限认证⽰例
如今,互联⽹项⽬对于安全的要求越来越严格,这就是对后端开发提出了更多的要求,⽬前⽐较成熟的⼏种⼤家⽐较熟悉的模式,像RBAC 基于⾓⾊权限的验证,shiro框架专门⽤于处理权限⽅⾯的,另⼀个⽐较流⾏的后端框架是Spring-Security,该框架提供了⼀整套⽐较成熟,也很完整的机制⽤于处理各类场景下的可以基于权限,资源路径,以及授权⽅⾯的解决⽅案,部分模块⽀持定制化,⽽且在和oauth2.0进⾏了很好的⽆缝连接,在移动互联⽹的授权认证⽅⾯有很强的优势,具体的使⽤⼤家可以结合⾃⼰的业务场景进⾏选取和使⽤
下⾯来说说关于单点登录中⽬前⽐较流⾏的⼀种使⽤⽅式,就是springsecurity+jwt实现⽆状态下⽤户登录;
JWT
在之前的篇章中⼤致提到过,使⽤jwt在分布式项⽬中进⾏⽤户信息的认证很⽅便,各个模块只需要知道配置的秘钥,就可以解密token中⽤户的基本信息,完成认证,很⽅便,关于使⽤jwt的基本内容可以查阅相关资料,或者参考我之前的⼀篇;
整理⼀下思路
1、搭建springboot⼯程
2、导⼊springSecurity跟jwt的依赖
3、⽤户的实体类,dao层,service层(真正开发时再写,这⾥就直接调⽤dao层操作数据库)
4、实现UserDetailsService接⼝
5、实现UserDetails接⼝
6、验证⽤户登录信息的
7、验证⽤户权限的
8、springSecurity配置
9、认证的Controller以及测试的controller
项⽬结构
pom⽂件
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- mvnrepository/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<!-- mvnrepository/artifact/org.springframework.security/spring-security-jwt -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
<!-- mvnrepository/artifact/org.springframework.security.oauth.boot/spring-security-oauth2-autoconfigure -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mybatis依赖 -->
<dependency>
<groupId&batis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
application.properties
server.port=8091
springframework和springboot#数据库连接
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true spring.datasource.sql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
#mybatis配置
mybatis.mapper-locations=classpath:mybatis/*.xml
#redis配置
spring.session.store-type=redis
spring,redis.host=127.0.0.1
为模拟⽤户登录,这⾥提前创建了⼀个测试使⽤的表,user
实体类User
public class User {
private Integer id;
private String username;
private String password;
private String role;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", role='" + role + '\'' +
'}';
}
}
Jwt⼯具类,⽤于管理token相关的操作,可以单测使⽤
public class TestJwtUtils {
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
public static final String SUBJECT = "congge";
public static final long EXPIRITION = 1000 * 24 * 60 * 60 * 7;
public static final String APPSECRET_KEY = "congge_secret";
private static final String ROLE_CLAIMS = "rol";
public static String generateJsonWebToken(Users user) {
if (Id() == null || UserName() == null || FaceImage() == null) {
return null;
}
Map<String,Object> map = new HashMap<>();
map.put(ROLE_CLAIMS, "rol");
String token = Jwts
.
builder()
.setSubject(SUBJECT)
.setClaims(map)
.claim("id", Id())
.claim("name", UserName())
.claim("img", FaceImage())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
.signWith(SignatureAlgorithm.HS256, APPSECRET_KEY)pact();
return token;
}
/
**
* ⽣成token
* @param username
* @param role
* @return
*/
public static String createToken(String username,String role) {
Map<String,Object> map = new HashMap<>();
map.put(ROLE_CLAIMS, role);
String token = Jwts
.builder()
.
setSubject(username)
.setClaims(map)
.claim("username",username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
.signWith(SignatureAlgorithm.HS256, APPSECRET_KEY)pact();
return token;
}
public static Claims checkJWT(String token) {
try {
final Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();  return claims;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取⽤户名
* @param token
* @return
*/
public static String getUsername(String token){
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
("username").toString();
}
/**
* 获取⽤户⾓⾊
* @param token
* @return
*/
public static String getUserRole(String token){
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
("rol").toString();
}
/**
* 是否过期
* @param token
* @return
*/
public static boolean isExpiration(String token){
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
Expiration().before(new Date());
}
public static void main(String[] args) {
String name = "acong";
String role = "rol";
String token = createToken(name,role);
System.out.println(token);
Claims claims = checkJWT(token);
System.out.("username"));
System.out.println(getUsername(token));
System.out.println(getUserRole(token));
System.out.println(isExpiration(token));
}
/**
* eyJhbGciOiJIUzI1NiJ9.
* eyJzdWIiOiJjb25nZ2UiLCJpZCI6IjExMDExIiwibmFtZSI6Im51b3dlaXNpa2kiLCJpbWciOiJ3d3cudW9rby5jb20vMS5wbmciLCJpYXQiOjE1NTQ5OTI1NzksImV4cCI6MTU1NTU5NzM3OX0.  * 6DJ9En-UBcTiMRldZeevJq3e1NxJgOWryUyim4_-tEE
*
* @param args
*/
/*public static void main(String[] args) {
Users user = new Users();
user.setId("11011");
user.setUserName("nuoweisiki");
user.setFaceImage("www.uoko/1.png");
String token = generateJsonWebToken(user);
System.out.println(token);
Claims claims = checkJWT(token);
if (claims != null) {
String id = ("id").toString();
String name = ("name").toString();
String img = ("img").toString();
String rol = ("rol").toString();
System.out.println("id:" + id);
System.out.println("name:" + name);
System.out.println("img:" + img);
System.out.println("rol:" + rol);
}
}*/
}
操作数据库的类
,这⾥主要是提供⽤户注册的⼀个save⽤户的⽅法,
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void save(User user) {
user.setId(1);
userDao.save(user);
}
}
JwtUser
该类封装登录⽤户相关信息,例如⽤户名,密码,权限集合等,需要实现UserDetails 接⼝,
public class JwtUser implements UserDetails {
private Integer id;
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;
public JwtUser() {
}
// 写⼀个能直接使⽤user创建jwtUser的构造器
public JwtUser(User user) {
id = Id();
username = Username();
password = Password();
authorities = Collections.singleton(new Role()));
}
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public String getPassword() {
return password;
}
public String getUsername() {
return username;
}
public boolean isAccountNonExpired() {
return true;
}
public boolean isAccountNonLocked() {
return true;
}
public boolean isCredentialsNonExpired() {
return true;
}
public boolean isEnabled() {
return true;
}
@Override
public String toString() {
return "JwtUser{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", authorities=" + authorities +
'}';
}
}
配置
JWTAuthenticationFilter
JWTAuthenticationFilter继承于UsernamePasswordAuthenticationFilter
该⽤于获取⽤户登录的信息,只需创建⼀个token并调⽤authenticationManager.authenticate()让s
pring-security去进⾏验证就可以了,不⽤⾃⼰查数据库再对⽐密码了,这⼀步交给spring去操作。这个操作有点像是shiro的subject.login(new UsernamePasswordToken()),验证的事情交给框架。
/**
* 验证⽤户名密码正确后,⽣成⼀个token,并将token返回给客户端
* 该类继承⾃UsernamePasswordAuthenticationFilter,重写了其中的2个⽅法 ,
* attemptAuthentication:接收并解析⽤户凭证。
* successfulAuthentication:⽤户成功登录后,这个⽅法会被调⽤,我们在这个⽅法⾥⽣成token并返回。
*/
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
super.setFilterProcessesUrl("/auth/login");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 从输⼊流中获取到登录的信息
try {
LoginUser loginUser = new ObjectMapper().InputStream(), LoginUser.class);
return authenticationManager.authenticate(
new Username(), Password())
)
;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
// 成功验证后调⽤的⽅法
// 如果验证成功,就⽣成token并返回
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
JwtUser jwtUser = (JwtUser) Principal();
System.out.println("jwtUser:" + String());
String role = "";
Collection<? extends GrantedAuthority> authorities = Authorities();
for (GrantedAuthority authority : authorities){
role = Authority();
}
String token = Username(), role);
//String token = Username(), false);
/
/ 返回创建成功的token
// 但是这⾥创建的token只是单纯的token
// 按照jwt的规定,最后请求的时候应该是 `Bearer token`
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
String tokenStr = JwtTokenUtils.TOKEN_PREFIX + token;
response.setHeader("token",tokenStr);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
}
}
JWTAuthorizationFilter
验证成功当然就是进⾏鉴权了,每⼀次需要权限的请求都需要检查该⽤户是否有该权限去操作该资源,当然这也是框架帮我们做的,那么我们需要做什么呢?很简单,只要告诉spring-security该⽤户是否已登录,是什么⾓⾊,拥有什么权限就可以了。
JWTAuthenticationFilter继承于BasicAuthenticationFilter,⾄于为什么要继承这个我也不太清楚了,这个我也是⽹上看到的其中⼀种实现,实在springSecurity苦⼿,不过我觉得不继承这个也没事呢(实现以下filter接⼝或者继承其他filter实现⼦类也可以吧)只要确保过滤器的顺序,JWTAuthorizationFilter在JWTAuthenticationFilter后⾯就没问题了。
/**
* 验证成功当然就是进⾏鉴权了
* 登录成功之后⾛此类进⾏鉴权操作
*/
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String tokenHeader = Header(TestJwtUtils.TOKEN_HEADER);
// 如果请求头中没有Authorization信息则直接放⾏了
if (tokenHeader == null || !tokenHeader.startsWith(TestJwtUtils.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// 如果请求头中有token,则进⾏解析,并且设置认证信息
super.doFilterInternal(request, response, chain);
}
// 这⾥从token中获取⽤户信息并新建⼀个token
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
String token = place(TestJwtUtils.TOKEN_PREFIX, "");
String username = Username(token);
String role = UserRole(token);
if (username != null){
return new UsernamePasswordAuthenticationToken(username, null,
Collections.singleton(new SimpleGrantedAuthority(role))
);
}
return null;
}
}
配置SpringSecurity

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。