session.css,基于Session的认证方式

1. 认证流程

基于Session认证方式的流程是,用户认证成功后,在服务端生成用户相关的数据保存在session中(当前会话)中,发给客户端对应的session_id存放到cookie中,这样用户请求时带上session_id就可以验证服务器端是否存在session数据,以完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了。

dfa36d0049b0

基于session的认证方式

基于Session的认证机制由Servlet规范定制,Servlet容器已经实现,用户通过HttpSession的操作方式即可实现,如下是HttpSession相关的操作API:

dfa36d0049b0

HttpSession相关的操作API

2. 创建工程

本案例工程使用maven进行构建,使用SpringMVC、Servlet3.0实现。

2.1 创建maven工程

创建maven工程security-springmvc,工程结构如下:

dfa36d0049b0

工程结构

2.2 Spring容器配置

在config包下定义ApplicationConfig,它对应web.xml中的ContextLoaderListener

/**

* 相当于apllicationContext.xml配置文件

*/

@Configuration

@ComponentScan(basePackages = "com.pengjs.book.admin.security",

excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})

public class ApplicationConfig {

// 配置除了Controller的其他bean,如:数据库连接池、事务管理器、业务bean等

}

2.3 ServletContext配置

本案例采用Servlet3.0无web.xml方式,在config包下定义WebConfig,它对应于DispatcherServlet配置

/**

* 相当于springmvc.xml配置文件

*/

@Configuration

@EnableWebMvc

@ComponentScan(basePackages = "com.pengjs.book.admin.security",

includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})

public class WebConfig implements WebMvcConfigurer {

@Autowired

private SimpleAuthenticationInterceptor simpleAuthenticationInterceptor;

/**

* 配置视图解析器

* @return

*/

@Bean

public InternalResourceViewResolver viewResolver() {

InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();

viewResolver.setPrefix("/WEB-INF/view/");

viewResolver.setSuffix(".jsp");

return viewResolver;

}

@Override

public void addViewControllers(ViewControllerRegistry registry) {

// 将"/"定向到"/login"

registry.addViewController("/").setViewName("login");

}

/**

* 添加拦截器,让拦截器生效

* @param registry

*/

@Override

public void addInterceptors(InterceptorRegistry registry) {

// login接口不拦截,只拦截:/r/**

registry.addInterceptor(simpleAuthenticationInterceptor).addPathPatterns("/r/**");

}

}

2.4 加载Spring容器

在init包下定义Spring容器初始化类SpringApplicationInitializer,此类实现WebApplicationInitializer接口,Spring容器启动时加载WebApplicationInitializer接口的所有实现类。

public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

/**

* spring容器,相当于加载apllicationContext.xml

* @return

*/

@Override

protected Class>[] getRootConfigClasses() {

// 指定rootContext的配置类

return new Class>[]{ApplicationConfig.class};

}

/**

* servletContext,相当于加载springmvc.xml配置文件

* @return

*/

@Override

protected Class>[] getServletConfigClasses() {

// 指定servletContext的配置类

return new Class>[]{WebConfig.class};

}

/**

* url-mapping

* @return

*/

@Override

protected String[] getServletMappings() {

return new String[] {"/"};

}

}

SpringApplicationInitializer相当于web.xml,使用了Servlet3.0开发则不需要要再定义web.xml,ApplicationConfig对应以下配置的application-context.xml,WebConfig对应以下配置的spring-mvc.xml,web.xml的参考内容:

org.springframework.web.context.ContextLoaderListener

contextConfigLocation

/WEB-INF/application-context.xml

springmvc

org.springframework.web.servlet.DispatcherServlet

contextConfigLocation

/WEB-INF/spring-mvc.xml

1

springmvc

/

2.5 实现认证功能

2.5.1 认证页面

在webapp/WEB-INF/view下定义login.jsp,本案例知识测试认证流程,没有添加css样式,页面实现可填入用户名、密码,触发登录提交表单信息至/login,内容如下:

用户登录

用户名:

密   码:

在WebConfig中添加如下配置,将“/”直接定向到login.jsp页面:

@Override

public void addViewControllers(ViewControllerRegistry registry) {

// 将"/"定向到"/login"

registry.addViewController("/").setViewName("login");

}

2.5.2 启动项目

pom配置:

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

com.pengjs.book.admin.security

security-springmvc

0.0.1-SNAPSHOT

war

UTF-8

1.8

1.8

org.springframework

spring-webmvc

5.1.5.RELEASE

javax.servlet

javax.servlet-api

3.1.0

provided

org.projectlombok

lombok

1.18.8

security-springmvc

org.apache.tomcat.maven

tomcat7-maven-plugin

2.2

org.apache.maven.plugins

maven-compiler-plugin

3.8.1

1.8

1.8

maven-resources-plugin

3.0.2

utf-8

true

src/main/resources

true

**/*

src/main/java

**/*.xml

配置maven启动:

dfa36d0049b0

配置maven启动

dfa36d0049b0

用户登录

2.5.3 认证接口

用户进入认证页面,熟肉账号和密码,点击登录,请求/login进行身份认证。

定义认证接口,此接口用于对传来的用户名、密码校验,若成功则返回该用户的详细信息,否则抛出异常:

public interface AuthenticationService {

/**

* 用户认证,校验用户信息是否合法

* @param authenticationRequest 用户认证请求,账号和密码

* @return 认证成功后的用户信息

*/

UserDto authentication(AuthenticationRequest authenticationRequest);

}

认证请求结构:

/**

* 用户身份信息

*/

@Data

public class AuthenticationRequest {

/**

* 用户名

*/

private String username;

/**

* 密码

*/

private String password;

}

认证成功后返回的用户详细信息,也就是当前登录用户的信息:

@Data

@AllArgsConstructor

@NoArgsConstructor

public class UserDto {

public static final String SESSION_USER_KEY = "_user";

private String id;

private String username;

private String password;

private String fullname;

private String mobile;

/**

* 用户权限

*/

private Set authorities;

}

认证接口实现类AuthenticationServiceImpl:

@Service

public class AuthenticationServiceImpl implements AuthenticationService {

@Override

public UserDto authentication(AuthenticationRequest authenticationRequest) {

// 校验参数是否为空

if (null == authenticationRequest || StringUtils.isEmpty(authenticationRequest.getUsername())

|| StringUtils.isEmpty(authenticationRequest.getPassword())) {

throw new RuntimeException("账号密码为空");

}

UserDto userDto = getUserDto(authenticationRequest.getUsername());

// 判断用户是否为空

if (null == userDto) {

throw new RuntimeException("查询不到该用户");

}

// 校验密码

if (!authenticationRequest.getPassword().equals(userDto.getPassword())) {

throw new RuntimeException("账号或者密码错误");

}

// 认证通过

return userDto;

}

/**

* 模拟用户查询

*

* @param username 用户名

* @return UserDto

*/

private UserDto getUserDto(String username) {

return userMap.get(username);

}

private Map userMap = new HashMap<>();

{

// 不同的用户,不同的权限

Set authorities1 = new HashSet<>();

// p1权限标识符,和/r/r1对应

authorities1.add("p1");

// p2权限标识符,和/r/r2对应

Set authorities2 = new HashSet<>();

authorities2.add("p2");

userMap.put("zhangsan", new UserDto("1000", "zhangsan", "123", "张三", "133433", authorities1));

userMap.put("lisi", new UserDto("1011", "lisi", "456", "李四", "144553", authorities2));

}

}

LoginController:

@RestController

public class LoginController {

@Autowired

private AuthenticationService authenticationService;

@PostMapping(value = "/login", produces = "text/plain;charset=utf-8")

public String login(AuthenticationRequest authenticationRequest, HttpSession session) {

UserDto userDto = authenticationService.authentication(authenticationRequest);

// 存入session

session.setAttribute(UserDto.SESSION_USER_KEY, userDto);

return userDto.getFullname() + "登录成功";

}

}

2.6 实现会话功能

会话是指用户登录系统后,系统会记住该用户的登录状态,他可以在系统连续操作直到退出系统的过程。

认证的目的是对系统资源的保护,每次对资源的访问,系统必须得直到是谁在访问资源,才能对该请求进行合法拦截。因此,在认证成功后,一般会把认证后的用户信息放入Session中,在后续的请求中,系统能够从Session中获取到当前用户,用这样的方式来实现会话机制。

增加会话控制

首先在UserDto中定义一个SESSION_USER_KEY,作为Session中存放登录用户信息的key。

public static final String SESSION_USER_KEY = "_user";

然后在LoginController中,认证成功后,将用户信息放入当前会话。并增加用户登出方法,登出时session只为无效。

/**

* 退出登录

* @param session

* @return

*/

@GetMapping(value = "logout", produces = {"text/plain;charset=utf-8"})

public String logout(HttpSession session) {

session.invalidate();

return "退出成功";

}

LoginController中增加测试资源,如果已经登录过则会在session中获取“_user”对应的UserDto信息,否则为空。

/**

* 测试资源1

* @param session

* @return

*/

@GetMapping(value = "/r/r1", produces = {"text/plain;charset=utf-8"})

public String r1(HttpSession session) {

String fullname = null;

Object userObj = session.getAttribute(UserDto.SESSION_USER_KEY);

if (null != userObj) {

fullname = ((UserDto) userObj).getFullname();

} else {

fullname = "匿名";

}

return fullname + " 访问资源1";

}

2.7 实现授权功能

现在我们已经完成了用户身份凭证及登录的状态保持,并且也知道了如何获取当前登录用户(从session中获取)的信息,接下来,用具访问系统需要经过授权,即需要完成以下功能:

匿名用户(未登录用户)访问拦截:禁止匿名用户访问某些资源。

登录用户访问拦截:根据用户的权限决定能否访问某些资源。

增加权限数据

为了实现这样的功能,需要在UserDto中增加权限属性,用于表示该登录用户所拥有的权限:

dfa36d0049b0

增加权限数据

在AuthenticationServiceImpl中为模拟用户初始化权限,其中给了张三p1权限,给了李四p2权限。

dfa36d0049b0

初始化用户权限

编写拦截器SimpleAuthenticationInterceptor

@Component

public class SimpleAuthenticationInterceptor implements HandlerInterceptor {

/**

* 调用Controller之前拦截

* 校验用户请求的URL是否在用户的权限范围内

* @param request

* @param response

* @param handler

* @return

* @throws Exception

*/

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

// 取出用户身份信息

Object object = request.getSession().getAttribute(UserDto.SESSION_USER_KEY);

if (null == object) {

// 没有认证,提示登录

writeContent(response, "请登录");

return false;

}

UserDto userDto = (UserDto) object;

// 根据请求的URL

String requestURI = request.getRequestURI();

if (userDto.getAuthorities().contains("p1") && requestURI.contains("/r/r1")) {

return true;

}

if (userDto.getAuthorities().contains("p2") && requestURI.contains("/r/r2")) {

return true;

}

writeContent(response, "没有权限,拒绝访问");

return false;

}

/**

* 响应信息给前端

* @param response

* @param msg

*/

private void writeContent(HttpServletResponse response, String msg) throws IOException {

response.setContentType("text/html;charset=utf-8");

PrintWriter writer = response.getWriter();

writer.write(msg);

writer.close();

}

}

WebConfig中注册拦截器,是拦截器生效:

dfa36d0049b0

注册拦截器

3. 小结

基于Session的认证方式是一种常见的认证方式,至今还有非常多的系统在使用。而在正常项目中,我们往往会考虑使用第三方安全框架(如spring security,shiro等)来实现认证授权功能,因为这样能一定程度提高生产力,提高软件标准化程度,另外,这下框架的可扩展性考虑的非常全面。但是缺点也非常明显,这些同用户组件为了提高支持范围会增加很多可能不需要的功能,结构上也会比较抽象,如果不了解它,一旦出现问题,将很难定位。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值