return (httpServletRequest, httpServletResponse, e)->{
Map<String,Object> map=new HashMap<>();
map.put(“state”,401);//SC_UNAUTHORIZED 的值为401
map.put(“message”,“请先登录再访问”);
WebUtils.writeJsonToClient(httpServletResponse,map);
};
}
}
定义业务对象,处理客户端的登陆请求,例如:
package sso.auth.service;
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.基于用户名从数据库查询用户信息
//SysUser user=userMapper.selectUserByUsername(username);//查数据库
if(!“jack”.equals(username))//假设这是从数据库查询的信息
throw new UsernameNotFoundException(“user not exists”);
//2.将用户信息封装到UserDetails对象中并返回
//假设这个密码是从数据库查询出来的
String encodedPwd=passwordEncoder.encode(“123456”);
//假设这个权限信息也是从数据库查询到的
//List permissions=userMapper.selectUserPermissions(username);//查数据库
//假如分配权限的方式是角色,编写字符串时用"ROLE_"做前缀
List grantedAuthorities =
AuthorityUtils.commaSeparatedStringToAuthorityList( “sys:res:retrieve,sys:res:create”);
//这个user是SpringSecurity提供的UserDetails接口的实现,用于封装用户信息
//后续我们也可以基于需要自己构建UserDetails接口的实现
User user=new User(username,encodedPwd,grantedAuthorities);
return user;//这里的返回值会交给springsecurity去校验
}
}
此对象编写好以后,可以启动服务基于postman进行登陆访问测试。
================================================================
第一步:定义JWT工具类,主要用于解析JWT令牌,例如
package sso.resource.util;
public class JwtUtils {
private static String secret=“AAABBBCCCDDDEEE”;
/*基于负载和算法创建token信息/
public static String generatorToken(Map<String,Object> map){
return Jwts.builder()
.setClaims(map)
.setExpiration(new Date(System.currentTimeMillis()+30601000))
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256,secret)
.compact();//签约,创建token
}
/*解析token获取数据/
public static Claims getClaimsFromToken(String token){
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
/*判定token是否失效/
public static boolean isTokenExpired(String token){
Date expiration=getClaimsFromToken(token).getExpiration();
return expiration.before(new Date());
}
}
第二步:定义Web工具类,用于向客户端响应json数据
package sso.auth.util;
public class WebUtils {
public static void writeJsonToClient(HttpServletResponse response, Map<String,Object> map)
throws IOException {
//1设置响应数据的编码
response.setCharacterEncoding(“utf-8”);
//2告诉浏览器响应数据的内容类型以及编码
response.setContentType(“application/json;charset=utf-8”);
//3获取输出流对象
PrintWriter out=response.getWriter();
//4 将map转换为json数据
String result=new ObjectMapper().writeValueAsString(map);
//5 将数据响应到客户端
out.println(result);
out.flush();
}
}
在资源服务中定义权限配置类,默认将所有认证请求放行,例如:
package sso.resource.config;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler());
http.authorizeRequests().anyRequest().permitAll();
}
//没有权限时执行此处理器方法
public AccessDeniedHandler accessDeniedHandler(){
return (httpServletRequest, httpServletResponse, e)-> {
Map<String,Object> map=new HashMap<>();
map.put(“state”,403);//SC_FORBIDDEN的值是403
map.put(“message”,“没有访问权限,请联系管理员”);
WebUtils.writeJsonToClient(httpServletResponse,map);
};
}
}
定义资源服务对象,用于处理客户端的资源访问服务,例如:
package sso.resource.controller;
@RestController
public class ResourceController {
@PreAuthorize(“hasAuthority(‘sys:res:create’)”)
@RequestMapping(“/doCreate”)
public String doCreate(HttpServletResponse response){
return “create resource (insert data) ok”;
}
/*查询操作/
@PreAuthorize(“hasAuthority(‘sys:res:retrieve’)”)
@RequestMapping(“/doRetrieve”)
public String doRetrieve(){//Retrieve 表示查询
return “query resource (select data) ok”;
}
/*修改操作/
@PreAuthorize(“hasAuthority(‘sys:res:update’)”)
@RequestMapping(“/doUpdate”)
public String doUpdate(){
return “update resource (update data) ok”;
}
/*删除操作/
@PreAuthorize(“hasAuthority(‘sys:res:delete’)”)
@RequestMapping(“/doDelete”)
public String doDelete(){
return “delete resource (dalete data) ok”;
}
}
资源服务器中的资源不是所有人都可以访问的,需要具备一定权限才可以,首先我们要判定是否登陆,然后判定登陆用户是否有权限,有访问权限才可以授权访问,这个操作可以放到spring mvc拦截器中进行实现,例如:
第一步:定义Spring MVC 拦截器.
package sso.resource.interceptor;
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token=request.getHeader(“token”);
if(token==null||“”.equals(token)) throw new RuntimeException(“请先登陆”);
if(JwtUtils.isTokenExpired(token)) throw new RuntimeException(“请先登陆”);
Claims claims=JwtUtils.getClaimsFromToken(token);
List list = (List) claims.get(“authorities”);
String[]authorities=list.toArray(new String[]{});
UserDetails userDetails= User.builder()
.username((String)claims.get(“username”))
.password(“”)
.authorities(authorities)
.build();
//将UserDetails对象保存到一个可以和Spring-Security交互的对象中
PreAuthenticatedAuthenticationToken authenticationToken=
new PreAuthenticatedAuthenticationToken(
userDetails,userDetails.getPassword(),
AuthorityUtils.createAuthorityList(authorities));
//将本次解析的用户详情和当前请求关联
//关联之后才能在后面的控制器中获得用户详情
authenticationToken.setDetails(new WebAuthenticationDetails(request));
//将当前用户详情保存到Spring-Security上下文
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
return true;
}
}
第二步:创建Spring Web配置类,用于注册和配置Spring MVC拦截器,例如:
package sso.resource.config;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor())
.addPathPatterns(“/**”);
}
}
第一步:启动认证服务器,通过postman进行登陆认证,例如:
第二步:启动资源服务器,并基于认证服务器返回的令牌进行资源访问
================================================================
当多个项目都有一部分公共资源需要重复编写时,我们可以创建一个公共工程,在这个工程中创建共性对象和依赖.其它工程需要时直接引用即可.
![在这里插入图片描述]( )初始化工程
第一步:添加项目依赖
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-security
io.jsonwebtoken
jjwt
0.9.1
第二步:拷贝工具类
将sso-auth工程中的WebUtils,JwtUtils拷贝到sso-common工程的sso.util包中
在前后端分离工程中,当通过前端工程访问认证服务和资源服务时,需要进行跨域配置,例如:
package sso.config;
@Configuration
public class CorsFilterConfig {
/*服务端过滤器层面的跨域设计/
@Bean
public FilterRegistrationBean filterFilterRegistrationBean(){
//1.对此过滤器进行配置(跨域设置-url,method)
UrlBasedCorsConfigurationSource configSource=new UrlBasedCorsConfigurationSource();
CorsConfiguration config=new CorsConfiguration();
config.addAllowedHeader(“*”);//所有请求头信息
config.addAllowedMethod(“*”);//所有请求方式,post,delete,get,put,…
config.addAllowedOrigin(“*”);//所有请求参数
config.setAllowCredentials(true);//所有认证信息,例如cookie
//2.注册过滤器并设置其优先级
configSource.registerCorsConfiguration(“/**”, config);
FilterRegistrationBean fBean=
new FilterRegistrationBean(
new CorsFilter(configSource));
//设置此过滤器的优先级最高
fBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return fBean;
}
}
第一步:删除sso-auth,sso-resource 工程下的util包
第二步:删除sso-auth,sso-resource 工程中的公共依赖
第三步:在sso-auth,sso-resource工程中添加通用工程依赖
com.cy.jt
sso-common
1.0-SNAPSHOT
打开postman进行重新登录,访问资源测试.
================================================================
我们做后端,一般在测试时直接基于postman进行访问就可以,为了更好理解前后端通讯过程,我们暂且基于springboot工程构建一个前端工程.
第一步:添加web依赖,代码如下:
org.springframework.boot
spring-boot-starter-web
第二步:创建application.yml配置文件,代码如下:
server:
port: 80
第三步:创建启动类,代码如下:
package sso;
@SpringBootApplication
public class UIApplication {
public static void main(String[] args) {
SpringApplication.run(UIApplication.class,args);
}
}
将课前资料中的static目录直接拷贝到项目中的resource目录下.
登录页面login.html内容如下:
<!doctype html>
Please Login
<button type=“button” @click=“doLogin()” class=“btn btn-primary”>Submit
登录成功页面index.html,代码如下:
Index Page
CRUD(Create,Retrieve,Update,Delete) Operation
- Retrieve(查询-select)
- Delete(删除-delete)
-
启动sso-auth,sso-resource,sso-ui 工程,然后访问http://localhost/login.html进行登录
输入正确的账号,密码执行登录,登录成功以后跳转到如下页面.
然后,对create,update选项进行访问,检测输出结果.
从登录认证,到资源访问,其过程如下:
=================================================================
目前我们登录时的账号,用户权限信息都是写死在UserServiceImpl类中的,实际项目中会从数据库查询用户以及用户对应权限信息.
实际项目中用户权限控制,通常是通过用户,角色,菜单以及他们的关系表进行数据存储,其业务描述如下:
其sql脚本如下:
DROP DATABASE IF EXISTS
jt_security
;CREATE DATABASE
jt_security
DEFAULT CHARACTER SET utf8mb4;SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
use
jt_security
;CREATE TABLE
sys_menu
(id
bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘ID’,name
varchar(50) NOT NULL COMMENT ‘权限名称’,permission
varchar(200) DEFAULT NULL COMMENT ‘权限标识’,PRIMARY KEY (
id
) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘权限表’;
CREATE TABLE
sys_role
(id
bigint(11) NOT NULL AUTO_INCREMENT COMMENT ‘角色ID’,role_name
varchar(50) NOT NULL COMMENT ‘角色名称’,PRIMARY KEY (
id
) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘角色表’;
CREATE TABLE
sys_role_menu
(id
bigint(11) NOT NULL AUTO_INCREMENT COMMENT ‘ID’,role_id
bigint(11) DEFAULT NULL COMMENT ‘角色ID’,menu_id
bigint(11) DEFAULT NULL COMMENT ‘权限ID’,PRIMARY KEY (
id
) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘角色与权限关系表’;
CREATE TABLE
sys_user
(id
bigint(11) NOT NULL AUTO_INCREMENT COMMENT ‘用户ID’,username
varchar(50) NOT NULL COMMENT ‘用户名’,password
varchar(100) DEFAULT NULL COMMENT ‘密码’,status
varchar(10) DEFAULT NULL COMMENT ‘状态 PROHIBIT:禁用 NORMAL:正常’,PRIMARY KEY (
id
) USING BTREE,UNIQUE KEY
username
(username
) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘系统用户表’;
CREATE TABLE
sys_user_role
(id
bigint(11) NOT NULL AUTO_INCREMENT COMMENT ‘ID’,user_id
bigint(11) DEFAULT NULL COMMENT ‘用户ID’,role_id
bigint(11) DEFAULT NULL COMMENT ‘角色ID’,PRIMARY KEY (
id
) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT=‘用户与角色关系表’;
INSERT INTO
sys_menu
VALUES (1, ‘select users’, ‘sys:res:create’);INSERT INTO
sys_menu
VALUES (2, ‘select menus’, ‘sys:res:retrieve’);INSERT INTO
sys_menu
VALUES (3, ‘select roles’, ‘sys:res:delete’);INSERT INTO
sys_role
VALUES (1, ‘ADMIN’);INSERT INTO
sys_role
VALUES (2, ‘USER’);INSERT INTO
sys_role_menu
VALUES (1, 1, 1);INSERT INTO
sys_role_menu
VALUES (2, 1, 2);INSERT INTO
sys_role_menu
VALUES (3, 1, 3);INSERT INTO
sys_role_menu
VALUES (4, 2, 1);INSERT INTO
sys_user
VALUES (1,‘admin’,‘$2a 10 10 10hIAewJVvpTdDSidROQmoXuBBucjLC7sxf7PDMWggZG49cKYhTXt16’,‘NORMAL’);INSERT INTO
sys_user
VALUES (2,‘user’,‘$2a 10 10 10hIAewJVvpTdDSidROQmoXuBBucjLC7sxf7PDMWggZG49cKYhTXt16’,‘NORMAL’);INSERT INTO
sys_user_role
VALUES (1, 1, 1);INSERT INTO
sys_user_role
VALUES (2, 2, 2);
第一步:在sso-auth工程中添加,如下依赖:
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.2.0
mysql
mysql-connector-java
8.0.21
org.springframework.boot
spring-boot-starter-test
test
第二步:修改sso-auth工程中配置文件,添加访问数据库部分,例如:
spring:
datasource:
url: jdbc:mysql:///jt_security?serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver #可以省略,默认会自动识别
第一步:创建UserMapper接口,并定义数据访问方法,代码如下:
package sso.dao;
@Mapper
public interface UserMapper {
/**
-
基于用户查询用户信息
-
@param username
-
@return 查询到的用户信息,表中的字段名会作为map中key,字段名对应的值会
-
作为map中的value进行存储
*/
@Select(“select * from sys_user where username=#{username}”)
Map<String,Object> selectUserByUsername(
@Param(“username”)String username);
/**
- 基于用户id查询用户权限信息
*/
@Select(" select distinct m.permission " +
" from sys_user u left join sys_user_role ur on u.id=ur.user_id " +
" left join sys_role_menu rm on ur.role_id=rm.role_id " +
" left join sys_menu m on rm.menu_id=m.id " +
" where u.id=#{id}")
List selectUserPermissions(@Param(“id”) Long id);
}
第二步:修改UserDetailServiceImpl类添加数据库访问操作,例如:
package sso.service;
/**
- 通过此对象处理登录请求
*/
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
//1.基于用户名查询用户信息
Map<String,Object> userMap=userMapper.selectUserByUsername(username);
if(userMap==null)throw new UsernameNotFoundException(“user not exists”);
//2.查询用户权限信息并封装查询结果
List userPermissions=
userMapper.selectUserPermissions((Long)userMap.get(“id”));
//权限信息后续要从数据库去查
return new User(username,
(String)userMap.get(“password”),//来自数据库
AuthorityUtils.createAuthorityList(//来自数据库
userPermissions.toArray(new String[]{})));
//这个值返回给谁?谁调用此方法这个就返回给谁.
}
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Java)
结尾
这不止是一份面试清单,更是一种”被期望的责任“,因为有无数个待面试者,希望从这篇文章中,找出通往期望公司的”钥匙“,所以上面每道选题都是结合我自身的经验于千万个面试题中经过艰辛的两周,一个题一个题筛选出来再次对好答案和格式做出来的,面试的答案也是再三斟酌,深怕误人子弟是小,影响他人仕途才是大过,也希望您能把这篇文章分享给更多的朋友,让他帮助更多的人,帮助他人,快乐自己,最后,感谢您的阅读。
由于细节内容实在太多啦,在这里我花了两周的时间把这些答案整理成一份文档了,在这里只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
ption(“user not exists”);//2.查询用户权限信息并封装查询结果
List userPermissions=
userMapper.selectUserPermissions((Long)userMap.get(“id”));
//权限信息后续要从数据库去查
return new User(username,
(String)userMap.get(“password”),//来自数据库
AuthorityUtils.createAuthorityList(//来自数据库
userPermissions.toArray(new String[]{})));
//这个值返回给谁?谁调用此方法这个就返回给谁.
}
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-xiFdOKEh-1713784132819)]
[外链图片转存中…(img-eisKn8cH-1713784132820)]
[外链图片转存中…(img-to0BLWpS-1713784132820)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Java)
[外链图片转存中…(img-bOTaY8SZ-1713784132821)]
结尾
[外链图片转存中…(img-8MnVym6o-1713784132821)]
这不止是一份面试清单,更是一种”被期望的责任“,因为有无数个待面试者,希望从这篇文章中,找出通往期望公司的”钥匙“,所以上面每道选题都是结合我自身的经验于千万个面试题中经过艰辛的两周,一个题一个题筛选出来再次对好答案和格式做出来的,面试的答案也是再三斟酌,深怕误人子弟是小,影响他人仕途才是大过,也希望您能把这篇文章分享给更多的朋友,让他帮助更多的人,帮助他人,快乐自己,最后,感谢您的阅读。
由于细节内容实在太多啦,在这里我花了两周的时间把这些答案整理成一份文档了,在这里只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取! -