创立我的项目
生成我的项目模板
为不便咱们初始化我的项目,Spring Boot给咱们提供一个我的项目模板生成网站。
1. 关上浏览器,拜访:https://start.spring.io/
2. 依据页面提醒,抉择构建工具,开发语言,我的项目信息等。
3. 点击 Generate,生成我的项目模板,生成之后会将压缩包下载到本地。
4. 解压demo.zip文件,而后idea导入pom.xml文件,我的项目构造如下:
增加相干依赖
增加 Maven 相干依赖,这里须要增加上web、swagger、spring security、jwt和fastjson的依赖,Swagge和fastjson的增加是为了不便接口测试。
<?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.4.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.spring.security</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!-- web --> <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> <!-- swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <!-- spring security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- jwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.58</version> </dependency> <!-- 集成lombok 框架 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> <!-- 打包时拷贝MyBatis的映射文件 --> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/sqlmap/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.*</include> </includes> <filtering>true</filtering> </resource> </resources> </build></project>
增加相干配置
- 增加swagger 配置
增加一个swagger 配置类,在工程下新建 config 包并增加一个 SwaggerConfig
配置类,除了惯例配置外,加了一个令牌属性,能够在接口调用的时候传递令牌。SwaggerConfig.java
package com.spring.security.demo.config;import java.util.ArrayList;import java.util.List;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import springfox.documentation.builders.ApiInfoBuilder;import springfox.documentation.builders.ParameterBuilder;import springfox.documentation.builders.PathSelectors;import springfox.documentation.builders.RequestHandlerSelectors;import springfox.documentation.schema.ModelRef;import springfox.documentation.service.ApiInfo;import springfox.documentation.service.Parameter;import springfox.documentation.spi.DocumentationType;import springfox.documentation.spring.web.plugins.Docket;import springfox.documentation.swagger2.annotations.EnableSwagger2;/** * @Author wulongbo * @Date 2020/11/21 11:50 * @Version 1.0 */@Configuration@EnableSwagger2public class SwaggerConfig { @Bean public Docket createRestApi(){ // 增加申请参数,咱们这里把token作为申请头部参数传入后端 ParameterBuilder parameterBuilder = new ParameterBuilder(); List<Parameter> parameters = new ArrayList<Parameter>(); parameterBuilder.name("Authorization").description("令牌").modelRef(new ModelRef("string")).parameterType("header") .required(false).build(); parameters.add(parameterBuilder.build()); return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()).build().globalOperationParameters(parameters); } private ApiInfo apiInfo(){ return new ApiInfoBuilder() .title("SpringBoot API Doc") .description("This is a restful api document of Spring Boot.") .version("1.0") .build(); }}
- 增加跨域 配置
增加一个CORS跨域配置类,在工程下新建 config 包并增加一个 CorsConfig
配置类。CorsConfig.java
package com.spring.security.demo.config;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.CorsRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/** * @Author wulongbo * @Date 2020/11/21 11:52 * @Version 1.0 */@Configurationpublic class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 容许跨域拜访的门路 .allowedOrigins("*") // 容许跨域拜访的源 .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE") // 容许申请办法 .maxAge(168000) // 预检间隔时间 .allowedHeaders("*") // 容许头部设置 .allowCredentials(true); // 是否发送cookie }}
平安配置类
上面这个配置类是Spring Security的要害配置。
在这个配置类中,咱们次要做了以下几个配置:
- 拜访门路URL的受权策略,如登录、Swagger拜访免登录认证等
- 指定了登录认证流程过滤器 JwtLoginFilter,由它来触发登录认证
- 指定了自定义身份认证组件 JwtAuthenticationProvider,并注入 UserDetailsService
- 指定了访问控制过滤器 JwtAuthenticationFilter,在受权时解析令牌和设置登录状态
- 指定了退出登录处理器,因为是前后端拆散,避免内置的登录处理器在后盾进行跳转
WebSecurityConfig.java
package com.spring.security.demo.config;import com.spring.security.demo.security.JwtAuthenticationFilter;import com.spring.security.demo.security.JwtAuthenticationProvider;import com.spring.security.demo.security.JwtLoginFilter;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;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.core.userdetails.UserDetailsService;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { // 应用自定义登录身份认证组件----> 指定了自定义身份认证组件 JwtAuthenticationProvider,并注入 UserDetailsService auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService)); } @Override protected void configure(HttpSecurity http) throws Exception { // 禁用 csrf, 因为应用的是JWT,咱们这里不须要csrf http.cors().and().csrf().disable() .authorizeRequests() // 跨域预检申请 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 登录URL .antMatchers("/login").permitAll() // swagger .antMatchers("/swagger**/**").permitAll() .antMatchers("/webjars/**").permitAll() .antMatchers("/v2/**").permitAll() // 其余所有申请须要身份认证 .anyRequest().authenticated(); // 退出登录处理器 http.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()); // 开启登录认证流程过滤器----> 指定了登录认证流程过滤器 JwtLoginFilter,由它来触发登录认证 http.addFilterBefore(new JwtLoginFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class); // 访问控制时登录状态查看过滤器----> 指定了访问控制过滤器 JwtAuthenticationFilter,在受权时解析令牌和设置登录状态 http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } }
登录认证触发过滤器
JwtLoginFilter 是在通过拜访 /login 的POST申请是被首先被触发的过滤器,默认实现是 UsernamePasswordAuthenticationFilter,它继承了 AbstractAuthenticationProcessingFilter,形象父类的 doFilter 定义了登录认证的大抵操作流程,这里咱们的 JwtLoginFilter 继承了 UsernamePasswordAuthenticationFilter,并进行了两个次要内容的定制。
- 覆写认证办法,批改用户名、明码的获取形式,具体起因看代码正文
- 覆写认证胜利后的操作,移除后盾跳转,增加生成令牌并返回给客户端
JwtLoginFilter.java
package com.spring.security.demo.security;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.nio.charset.Charset;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import com.spring.security.demo.utils.HttpUtils;import com.spring.security.demo.utils.JwtTokenUtils;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;/** * 启动登录认证流程过滤器 * @author Wulongbo * @date Nov 28, 2020 */public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter { public JwtLoginFilter(AuthenticationManager authManager) { setAuthenticationManager(authManager); } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { // POST 申请 /login 登录时拦挡, 由此办法触发执行登录认证流程,能够在此覆写整个登录认证逻辑 super.doFilter(req, res, chain); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // 能够在此覆写尝试进行登录认证的逻辑,登录胜利之后等操作不再此办法内 // 如果应用此过滤器来触发登录认证流程,留神登录申请数据格式的问题 // 此过滤器的用户名明码默认从request.getParameter()获取,然而这种 // 读取形式不能读取到如 application/json 等 post 申请数据,须要把 // 用户名明码的读取逻辑批改为到流中读取request.getInputStream() String body = getBody(request); JSONObject jsonObject = JSON.parseObject(body); String username = jsonObject.getString("username"); String password = jsonObject.getString("password"); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); JwtAuthenticatioToken authRequest = new JwtAuthenticatioToken(username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { // 存储登录认证信息到上下文 SecurityContextHolder.getContext().setAuthentication(authResult); // 记住我服务 getRememberMeServices().loginSuccess(request, response, authResult); // 触发事件监听器 if (this.eventPublisher != null) { eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass())); } // 生成并返回token给客户端,后续拜访携带此token JwtAuthenticatioToken token = new JwtAuthenticatioToken(null, null, JwtTokenUtils.generateToken(authResult)); HttpUtils.write(response, token); } /** * 获取申请Body * @param request * @return */ public String getBody(HttpServletRequest request) { StringBuilder sb = new StringBuilder(); InputStream inputStream = null; BufferedReader reader = null; try { inputStream = request.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); }}
登录控制器
除了应用下面的登录认证过滤器拦挡 /login Post申请之外,咱们也能够不应用下面的过滤器,通过自定义登录接口实现,只有在登录接口手动触发登录流程并生产令牌即可。
其实 Spring Security 的登录认证过程只需调用 AuthenticationManager 的 authenticate(Authentication authentication) 办法,最终返回认证胜利的 Authentication 实现类并存储到SpringContexHolder 上下文即可,这样前面受权的时候就能够从 SpringContexHolder 中获取登录认证信息,并依据其中的用户信息和权限信息决定是否进行受权。LoginController.java
package com.spring.security.demo.controller;import com.spring.security.demo.security.JwtAuthenticatioToken;import com.spring.security.demo.utils.SecurityUtils;import com.spring.security.demo.vo.HttpResult;import com.spring.security.demo.vo.LoginBean;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import javax.servlet.http.HttpServletRequest;import java.io.IOException;/** * @Author wulongbo * @Date 2020/11/24 10:05 * @Version 1.0 */ @RestController public class LoginController { @Autowired private AuthenticationManager authenticationManager; /** * 登录接口 */ @PostMapping(value = "/login") public HttpResult login(@RequestBody LoginBean loginBean, HttpServletRequest request) throws IOException { String username = loginBean.getUsername(); String password = loginBean.getPassword(); // 零碎登录认证 JwtAuthenticatioToken token = SecurityUtils.login(request, username, password, authenticationManager); return HttpResult.ok(token); }}
如下是登录认证的逻辑, 能够看到局部逻辑跟下面的登录认证过滤器差不多。
- 执行登录认证过程,通过调用 AuthenticationManager 的 authenticate(token) 办法实现
- 将认证胜利的认证信息存储到上下文,供后续拜访受权的时候获取应用
- 通过JWT生成令牌并返回给客户端,后续拜访和操作都须要携带此令牌
无关登录过程的逻辑,参见SecurityUtils的login办法。
SecurityUtils.java
package com.spring.security.demo.utils;import javax.servlet.http.HttpServletRequest;import com.spring.security.demo.security.JwtAuthenticatioToken;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.core.Authentication;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;/** * Security相干操作 * @author Wulongbo * @date Jun 29, 2020 */public class SecurityUtils { /** * 零碎登录认证 * @param request * @param username * @param password * @param authenticationManager * @return */ public static JwtAuthenticatioToken login(HttpServletRequest request, String username, String password, AuthenticationManager authenticationManager) { JwtAuthenticatioToken token = new JwtAuthenticatioToken(username, password); token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // 执行登录认证过程 Authentication authentication = authenticationManager.authenticate(token); // 认证胜利存储认证信息到上下文 SecurityContextHolder.getContext().setAuthentication(authentication); // 生成令牌并返回给客户端 token.setToken(JwtTokenUtils.generateToken(authentication)); return token; } /** * 获取令牌进行认证 * @param request */ public static void checkAuthentication(HttpServletRequest request) { // 获取令牌并依据令牌获取登录认证信息 Authentication authentication = JwtTokenUtils.getAuthenticationeFromToken(request); // 设置登录认证信息到上下文 SecurityContextHolder.getContext().setAuthentication(authentication); } /** * 获取以后用户名 * @return */ public static String getUsername() { String username = null; Authentication authentication = getAuthentication(); if(authentication != null) { Object principal = authentication.getPrincipal(); if(principal != null && principal instanceof UserDetails) { username = ((UserDetails) principal).getUsername(); } } return username; } /** * 获取用户名 * @return */ public static String getUsername(Authentication authentication) { String username = null; if(authentication != null) { Object principal = authentication.getPrincipal(); if(principal != null && principal instanceof UserDetails) { username = ((UserDetails) principal).getUsername(); } } return username; } /** * 获取以后登录信息 * @return */ public static Authentication getAuthentication() { if(SecurityContextHolder.getContext() == null) { return null; } Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return authentication; } }
令牌生成器
咱们令牌是应用JWT生成的,令牌生成的逻辑,参见源码JwtTokenUtils的generateToken相干办法。
JwtTokenUtils.java
package com.spring.security.demo.utils;import java.io.Serializable;import java.util.ArrayList;import java.util.Date;import java.util.HashMap;import java.util.List;import java.util.Map;import javax.servlet.http.HttpServletRequest;import com.spring.security.demo.security.GrantedAuthorityImpl;import com.spring.security.demo.security.JwtAuthenticatioToken;import org.springframework.security.core.Authentication;import org.springframework.security.core.GrantedAuthority;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;/** * JWT工具类 * @author Wulongbo * @date Jun 29, 2020 */public class JwtTokenUtils implements Serializable { private static final long serialVersionUID = 1L; /** * 用户名称 */ private static final String USERNAME = Claims.SUBJECT; /** * 创立工夫 */ private static final String CREATED = "created"; /** * 权限列表 */ private static final String AUTHORITIES = "authorities"; /** * 密钥 */ private static final String SECRET = "abcdefgh"; /** * 有效期12小时 */ private static final long EXPIRE_TIME = 12 * 60 * 60 * 1000; /** * 生成令牌 * * @param authentication 用户 * @return 令牌 */ public static String generateToken(Authentication authentication) { Map<String, Object> claims = new HashMap<>(3); claims.put(USERNAME, SecurityUtils.getUsername(authentication)); claims.put(CREATED, new Date()); claims.put(AUTHORITIES, authentication.getAuthorities()); return generateToken(claims); } /** * 从数据申明生成令牌 * * @param claims 数据申明 * @return 令牌 */ private static String generateToken(Map<String, Object> claims) { Date expirationDate = new Date(System.currentTimeMillis() + EXPIRE_TIME); return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, SECRET).compact(); } /** * 从令牌中获取用户名 * * @param token 令牌 * @return 用户名 */ public static String getUsernameFromToken(String token) { String username; try { Claims claims = getClaimsFromToken(token); username = claims.getSubject(); } catch (Exception e) { username = null; } return username; } /** * 依据申请令牌获取登录认证信息 * @param token 令牌 * @return 用户名 */ public static Authentication getAuthenticationeFromToken(HttpServletRequest request) { Authentication authentication = null; // 获取申请携带的令牌 String token = JwtTokenUtils.getToken(request); if(token != null) { // 申请令牌不能为空 if(SecurityUtils.getAuthentication() == null) { // 上下文中Authentication为空 Claims claims = getClaimsFromToken(token); if(claims == null) { return null; } String username = claims.getSubject(); if(username == null) { return null; } if(isTokenExpired(token)) { return null; } Object authors = claims.get(AUTHORITIES); List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); if (authors != null && authors instanceof List) { for (Object object : (List) authors) { authorities.add(new GrantedAuthorityImpl((String) ((Map) object).get("authority"))); } } authentication = new JwtAuthenticatioToken(username, null, authorities, token); } else { if(validateToken(token, SecurityUtils.getUsername())) { // 如果上下文中Authentication非空,且申请令牌非法,间接返回以后登录认证信息 authentication = SecurityUtils.getAuthentication(); } } } return authentication; } /** * 从令牌中获取数据申明 * * @param token 令牌 * @return 数据申明 */ private static Claims getClaimsFromToken(String token) { Claims claims; try { claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } catch (Exception e) { claims = null; } return claims; } /** * 验证令牌 * @param token * @param username * @return */ public static Boolean validateToken(String token, String username) { String userName = getUsernameFromToken(token); return (userName.equals(username) && !isTokenExpired(token)); } /** * 刷新令牌 * @param token * @return */ public static String refreshToken(String token) { String refreshedToken; try { Claims claims = getClaimsFromToken(token); claims.put(CREATED, new Date()); refreshedToken = generateToken(claims); } catch (Exception e) { refreshedToken = null; } return refreshedToken; } /** * 判断令牌是否过期 * * @param token 令牌 * @return 是否过期 */ public static Boolean isTokenExpired(String token) { try { Claims claims = getClaimsFromToken(token); Date expiration = claims.getExpiration(); return expiration.before(new Date()); } catch (Exception e) { return false; } } /** * 获取申请token * @param request * @return */ public static String getToken(HttpServletRequest request) { String token = request.getHeader("Authorization"); String tokenHead = "Bearer "; if(token == null) { token = request.getHeader("token"); } else if(token.contains(tokenHead)){ token = token.substring(tokenHead.length()); } if("".equals(token)) { token = null; } return token; }}
登录身份认证组件
下面说到登录认证是通过调用 AuthenticationManager 的 authenticate(token) 办法实现的,而 AuthenticationManager 又是通过调用 AuthenticationProvider 的 authenticate(Authentication authentication) 来实现认证的,所以通过定制 AuthenticationProvider 也能够实现各种自定义的需要,咱们这里只是简略的继承 DaoAuthenticationProvider 展现如何自定义,具体的大家能够依据各自的需要按需定制。
JwtAuthenticationProvider.java
package com.spring.security.demo.security;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.authentication.dao.DaoAuthenticationProvider;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;/** * 身份验证提供者 * @author Wulongbo * @date Nov 20, 2020 */public class JwtAuthenticationProvider extends DaoAuthenticationProvider { public JwtAuthenticationProvider(UserDetailsService userDetailsService) { setUserDetailsService(userDetailsService); setPasswordEncoder(new BCryptPasswordEncoder()); } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 能够在此处覆写整个登录认证逻辑 return super.authenticate(authentication); } @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { // 能够在此处覆写明码验证逻辑 super.additionalAuthenticationChecks(userDetails, authentication); }}
咱们自定义的 UserDetailsService,从咱们的用户服务 UserService 中获取用户和权限信息。
UserDetailsServiceImpl.java
package com.spring.security.demo.security;import java.util.List;import java.util.Set;import java.util.stream.Collectors;import com.spring.security.demo.model.User;import com.spring.security.demo.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.GrantedAuthority;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;/** * 用户登录认证信息查问 * @author Wulongbo * @date Jun 29, 2020 */@Servicepublic class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserService userService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userService.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("该用户不存在"); } // 用户权限列表,依据用户领有的权限标识与如 @PreAuthorize("hasAuthority('sys:menu:view')") 标注的接口比照,决定是否能够调用接口 Set<String> permissions = userService.findPermissions(username); List<GrantedAuthority> grantedAuthorities = permissions.stream().map(GrantedAuthorityImpl::new).collect(Collectors.toList()); return new JwtUserDetails(username, user.getPassword(), grantedAuthorities); }}
用户认证信息
下面 UserDetailsService 加载好用户认证信息后会封装认证信息到一个 UserDetails 的实现类。
默认实现是 User 类,咱们这里没有非凡须要,简略继承即可,简单需要能够在此基础上进行拓展。
JwtUserDetails.java
package com.spring.security.demo.security;import java.util.Collection;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.userdetails.User;/** * 平安用户模型 * @author Wulongbo * @date Jun 29, 2020 */public class JwtUserDetails extends User { private static final long serialVersionUID = 1L; public JwtUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities) { this(username, password, true, true, true, true, authorities); } public JwtUserDetails(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); }}
用户操作代码
简略的用户模型,蕴含用户名明码。
User.java
package com.spring.security.demo.model;import lombok.Data;/** * 用户模型 * @author Wulongbo * @date Jun 29, 2020 */@Datapublic class User { private Long id; private String username; private String password;}
用户服务接口,只提供简略的用户查问和权限查问接口用于模仿。
UserService.java
package com.spring.security.demo.service;import com.spring.security.demo.model.User;import java.util.Set;/** * 用户治理 * @author Wulongbo * @date Jun 29, 2020 */public interface UserService { /** * 依据用户名查找用户 * @param username * @return */ User findByUsername(String username); /** * 查找用户的菜单权限标识汇合 * @param username * @return */ Set<String> findPermissions(String username);}
用户服务实现,只简略获取返回模仿数据,理论场景依据状况从DAO获取即可。
SysUserServiceImpl.java
package com.spring.security.demo.service.impl;import java.util.HashSet;import java.util.Set;import com.spring.security.demo.model.User;import com.spring.security.demo.service.UserService;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.stereotype.Service;@Servicepublic class SysUserServiceImpl implements UserService { @Override public User findByUsername(String username) { User user = new User(); user.setId(1L); user.setUsername(username); String password = new BCryptPasswordEncoder().encode("123"); user.setPassword(password); return user; } @Override public Set<String> findPermissions(String username) { Set<String> permissions = new HashSet<>(); permissions.add("sys:user:view"); permissions.add("sys:user:add"); permissions.add("sys:user:edit"); permissions.add("sys:user:delete"); return permissions; }}
用户控制器,提供三个测试接口,其中权限列表中未蕴含删除接口定义的权限('sys:user:delete'),登录之后也将无权限调用。
UserController.java
package com.spring.security.demo.controller;import com.spring.security.demo.vo.HttpResult;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * 用户控制器 * @author Wulongbo * @date Jun 29, 2020 */@RestController@RequestMapping("user")public class UserController { @PreAuthorize("hasAuthority('sys:user:view')") @GetMapping(value="/findAll") public HttpResult findAll() { return HttpResult.ok("the findAll service is called success."); } @PreAuthorize("hasAuthority('sys:user:edit')") @GetMapping(value="/edit") public HttpResult edit() { return HttpResult.ok("the edit service is called success."); } @PreAuthorize("hasAuthority('sys:user:delete')") @GetMapping(value="/delete") public HttpResult delete() { return HttpResult.ok("the delete service is called success."); }}
登录认证查看过滤器
拜访接口的时候,登录认证查看过滤器 JwtAuthenticationFilter 会拦挡申请校验令牌和登录状态,并依据状况设置登录状态。
JwtAuthenticationFilter.java
package com.spring.security.demo.security;import java.io.IOException;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import com.spring.security.demo.utils.SecurityUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;/** * 登录认证查看过滤器 * @author Wulongbo * @date Jun 29, 2020 */public class JwtAuthenticationFilter extends BasicAuthenticationFilter { @Autowired public JwtAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // 获取token, 并查看登录状态 SecurityUtils.checkAuthentication(request); chain.doFilter(request, response); } }
具体具体获取token和查看登录状态代码请查看SecurityUtils的checkAuthentication办法。
编译测试运行
- 我的项目目录构造如下,Maven install,开始执行Maven构建,如果呈现如下信息,就阐明我的项目编译打包胜利了。
- 关上文件文件 DemoApplication.java -> Run DemoApplication,开始启动利用,当呈现如下信息的时候,就阐明利用启动胜利了,默认启动端口是8080
- 关上浏览器,拜访:http://localhost:8080/swagger-ui.html,进入swagger接口文档界面。