本篇做为SpringBoot2.1版本的我的开发框架 子章节,请先阅读SpringBoot2.1版本的我的开发框架再次阅读本篇文章html
后端项目地址:SpringBoot2.1版本的我的应用开发框架前端
前端项目地址:ywh-vue-adminvue
感谢PanJiaChen大神给咱们建立了这么好的vue后端管理模板,大神有一系列的教程,在预览地址中有系列文章的地址,还有项目github的地址,感受大神就是帅气。java
vue-element-admin预览地址:vue-element-adminnode
vue官方文档:vue官方文档git
node安装:node.js 安装与环境变量配置github
下载PanJiaChen大神的vue-admin-template,这个是大神推荐的二次开发的模板,vue-element-admin大神但愿是一个集成方案,当咱们须要什么再去拿什么。redis
这里我不对项目结构作介绍,在大神的系列文章中都有介绍,当咱们把项目下载好之后,咱们尝试的在本地跑起来,肯定没有错误之后咱们再进行下一步。spring
在下载好的项目中运行cmd,先下载项目所须要的依赖后再启动数据库
npm install
。。。。
npm run dev
复制代码
效果图,如今登录的仍是默认的用户,咱们要实现的功能是:前端与后端作交互,并在数据库中查询用户时候否是有权限登录。
跑起来后登录的界面如上图,没有预览的功能多,因此之后咱们按照咱们本身想要的需求一一加进去。
咱们想要后端与前端交互起来,其实仍是须要修改挺多地方的,这里先介绍修改后端,在先后端分离的项目中多数用Token来作请求的认证,我也是实现了jwt和SpringSecurity来保护API,他们俩在我理解来看是没有直接关系的,而是合做的关系,由SpringSecurity来决定什么请求能够访问咱们服务器,能够访问的请求再由jwt来判断是否携带Token,没有携带的不予经过,再加上网上不少都是经过这种模式来实现的,参考的资料也比较多。
推荐: 重拾后端之Spring Boot(四):使用JWT和Spring Security保护REST API
在security模块中的application-security.yml文件中添加如下内容,jwt的加密字符串是一个提早写好的,这里就至关于配置了三个常量,并无什么特别的,以后会在类中加载,若是闲麻烦,能够直接在类中定义常量便可。
jwt:
header: token #jwt的请求头
secret: eyJleHAiOjE1NDMyMDUyODUsInN1YiI6ImFkbWluIiwiY3Jl #jwt的加密字符串
expiration: 3600000 #jwt token有效时间(毫秒)一个小时
复制代码
在ywh-starter-security模块的utils包中建立JwtTokenUtil工具类,若是想看详细的代码,能够前往个人GitHub查看详细代码。
package com.ywh.security.utils;
/** * CreateTime: 2019-01-22 10:27 * ClassName: JwtTokenUtil * Package: com.ywh.security.utils * Describe: * jwt的工具类 * * @author YWH */
@Data
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtTokenUtil {
private String secret;
private Long expiration;
private String header;
/** * 从数据声明生成令牌 * * @param claims 数据声明 * @return 令牌 */
private String generateToken(Map<String, Object> claims) {
Date expirationDate = new Date(System.currentTimeMillis() + expiration);
return Jwts.builder()
.setClaims(claims)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
/** * 生成令牌 * @return 令牌 */
public String generateToken(String userName) {
Map<String, Object> claims = new HashMap<>(2);
claims.put("sub", userName);
claims.put("created", new Date());
return generateToken(claims);
}
。。。。。。。。。。。。中间省略了代码
}
复制代码
在咱们前端向后端请求时,咱们要每一次的判断是否携带了token,这个任务咱们就交给拦截器来执行,建立JwtAuthenticationTokenFilter拦截器
package com.ywh.security.filter;
/** * CreateTime: 2019-01-29 18:15 * ClassName: JwtAuthenticationTokenFilter * Package: com.ywh.security.filter * Describe: * spring的拦截器 * * @author YWH */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private final static Logger log = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
private JwtTokenUtil jwtTokenUtil;
private UserDetailsService userDetailsService;
@Autowired
public JwtAuthenticationTokenFilter(JwtTokenUtil jwtTokenUtil, UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
}
/** * 该拦截器主要的功能是,拦截请求后,判断是否携带token,若是未携带token则不予经过。 * @param httpServletRequest http请求 * @param httpServletResponse http响应 * @param filterChain 拦截器 * @throws ServletException 异常信息 * @throws IOException 异常信息 */
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
// 获取request中jwt token
String authHeader = httpServletRequest.getHeader(jwtTokenUtil.getHeader());
// 验证token是否存在
if(StringUtils.isNotEmpty(authHeader)){
//根据token获取用户名
String userName = jwtTokenUtil.getUsernameFromToken(authHeader);
if(userName != null && SecurityContextHolder.getContext().getAuthentication() == null){
// 经过用户名 获取用户的信息
UserDetails userDetails = userDetailsService.loadUserByUsername(userName);
// 验证token和用户信息是否匹配
if(jwtTokenUtil.validateToken(authHeader,userDetails)){
// 而后构造UsernamePasswordAuthenticationToken对象
// 最后绑定到当前request中,在后面的请求中就能够获取用户信息
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
复制代码
拦截器写好之后,咱们须要修改SecurityConfigurer类中configure(HttpSecurity httpSecurity)方法,这个类在我上两篇文章中都有介绍,如下代码中我写了跨域请求的后端实现。后面咱们就不用在前端实现跨域请求的设置了,不过我也会把前端如何实现跨域写出来的。
/** * 配置如何经过拦截器保护咱们的请求,哪些能经过哪些不能经过,容许对特定的http请求基于安全考虑进行配置 * @param httpSecurity http * @throws Exception 异常 */
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// 暂时禁用csrc不然没法提交
.csrf().disable()
// session管理
.sessionManagement()
// 咱们使用SessionCreationPolicy.STATELESS无状态的Session机制(即Spring不使用HTTPSession),对于全部的请求都作权限校验,
// 这样Spring Security的拦截器会判断全部请求的Header上有没有”X-Auth-Token”。
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 设置最多一个用户登陆,若是第二个用户登录则第一用户被踢出,并跳转到登录页面
.maximumSessions(1).expiredUrl("/login.html");
httpSecurity
// 开始认证
.authorizeRequests()
// 对静态文件和登录页面放行
.antMatchers("/static/**").permitAll()
.antMatchers("/auth/**").permitAll()
.antMatchers("/login.html").permitAll()
// 其余请求须要认证登录
.anyRequest().authenticated();
// 注入咱们刚才写好的 jwt过滤器,添加在UsernamePasswordAuthenticationFilter过滤器以前
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 这块是配置跨域请求的
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
// 让Spring security放行全部preflight request
registry.requestMatchers(CorsUtils::isPreFlightRequest).permitAll();
}
/** * 这块是配置跨域请求的 * @return Cors过滤器 */
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
final CorsConfiguration cors = new CorsConfiguration();
cors.setAllowCredentials(true);
cors.addAllowedOrigin("*");
cors.addAllowedHeader("*");
cors.addAllowedMethod("*");
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", cors);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
复制代码
能够看到上面代码中,我把咱们实现的拦截器放到了SpringSecurity的拦截器链中去了,这就使他们俩有了合做的关系,接下来就是建立咱们的service和controller,实现最基本的登录和退出,用户登录后返回一个Token,前端存在本地缓存(localStorage)或者sessionStorage中,以供以后的请求使用。
package com.ywh.security.service.impl;
/** * CreateTime: 2019-01-25 * ClassName: SysUserServiceImpl * Package: com.ywh.security.service.impl * Describe: * 业务逻辑接口的实现类 * @author YWH */
@Service
public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntity> implements SysUserService {
private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class);
@Autowired
private SysUserDao dao;
@Autowired
private AuthenticationManager authenticate;
@Autowired
private JwtTokenUtil jwtTokenUtil;
/** * 获取用户详细信息 * @param username 用户名 * @return 实体类 */
@Override
public SysUserEntity findUserInfo(String username) {
return dao.selectByUserName(username);
}
/** * 用户登录 * @param username 用户名 * @param password 密码 * @return 登录成功 返回token */
@Override
public String login(String username, String password) throws AuthenticationException {
// 内部登陆请求
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// 验证是否有权限
Authentication auth = authenticate.authenticate(authRequest);
log.debug("===============权限============" + auth);
SecurityContextHolder.getContext().setAuthentication(auth);
return jwtTokenUtil.generateToken(username);
}
}
复制代码
Controller的实现,由于都是最简单的实现,能够根据本身的需求修改,后期也能够再根据本身的想法加相应的实现便可。
package com.ywh.security.controller;
/** * CreateTime: 2019-01-28 16:06 * ClassName: AuthController * Package: com.ywh.security.controller * Describe: * 权限控制器 * * @author YWH */
@RestController
@RequestMapping("auth")
public class AuthController {
private static final Logger LOG = LoggerFactory.getLogger(AuthController.class);
@Autowired
private SysUserService sysUserService;
/** * 登录 * @param map 接收体 * @return 返回token */
@PostMapping("login")
public Result login(@RequestBody Map<String, String> map){
try {
String token = sysUserService.login(map.get("username"), map.get("password"));
return Result.successJson(token);
}catch (AuthenticationException ex){
LOG.error("登录失败",ex);
return Result.errorJson(BaseEnum.PASSWORD_ERROR.getMsg(),BaseEnum.PASSWORD_ERROR.getIndex());
}
}
/** * 用户详情 * @return 用户详细信息 */
@Cacheable(value = "userInfo")
@GetMapping("userInfo")
public Result userInfo(){
Object authentication = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if(authentication instanceof SecurityUserDetails){
return Result.successJson(sysUserService.findUserInfo(((SecurityUserDetails) authentication).getUsername()));
}
return Result.errorJson(BaseEnum.LOGIN_AGIN.getMsg(),BaseEnum.LOGIN_AGIN.getIndex());
}
@PostMapping("logOut")
public Result logOut(){
return Result.successJson("退出成功,由于token自己是无状态,若是经过redis来控制token的生存周期,则变成了有状态,因此暂时没有好的解决办法。");
}
}
复制代码
到此就结束了后端项目的修改,我觉的最重要的是要明白它们是怎么样的工做流程,知道流程后咱们就好理解不少,一步一步往下写就能够了,碰到不会的多Google多百度便可。
jwt和SpringSecurity的流程总结:
在上面咱们把前端项目vue-elment-template跑起来后,须要修改挺多地方的,比较杂,我也是遇到一个错误修改一个地方。
在后端项目中我已经实现了后端跨域的方法,可是前端也是能够实现跨域请求的,二者选择哪一个均可以。
proxyTable: {
'/core': {
target: 'http://192.168.0.117:8082', // 接口的域名
// secure: false, // 若是是https接口,须要配置这个参数
changeOrigin: true, // 若是接口跨域,须要进行这个参数配置
pathRewrite: {
'^/core': '/core'
}
}
},
复制代码
'use strict'
module.exports = {
NODE_ENV: '"production"',
BASE_API: '"http://localhost:8082/core/"',
}
复制代码
export function login(username, password) {
return request({
url: '/auth/login',
method: 'post',
data: {
username,
password
}
})
}
复制代码
以上差很少就是我在前端遇到的大问题,颇有不少小问题,就不一一贴了,设置了以上后,能够先试一试能不能跑起来,若是不行,能够对比我在GitHub的代码。
当咱们再次登录时,能够看到咱们已是在数据库中查询用户信息而且登录了。
若是再以默认的admin登录则显示用户名或密码错误