1.1认证流程
基于Session认证方式的流程,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话),而发给客户端。
基于Session的认证机制由servlet规范定制,servlet容器已实现,用户通过httpsession的操作方法即可实现,如下是httpsession相关的api
方法 | 含义 |
HttpSession getSession(Boolean create) | 获取当前httpsession对象 |
void setAttributes(String name,Object value) | 向session对象中存放对象 |
object getAttributes(String name) | 从session中获取对象 |
void invalidate | 使用httpsession失效 |
1.2创建工程
本案例工程使用maven进行构建,使用Spring MVC,servlet 3.0实现
创建maven工程security-springmvc,工程结构如下:
pom.xml的配置
4.0.0com.wangjunji.securitysecurity-springmvc1.0-SNAPSHOTwarsecurity-springmvc Maven Webapphttp://www.example.comUTF-81.81.8org.springframeworkspring-webmvc5.1.5.RELEASEjavax.servletjavax.servlet-api3.0.1org.projectlomboklombok1.18.8junitjunit4.11testsecurity-springmvcmaven-clean-plugin3.1.0maven-resources-plugin3.0.2maven-compiler-plugin3.8.0maven-surefire-plugin2.22.1maven-war-plugin3.2.2maven-install-plugin2.5.2maven-deploy-plugin2.8.2
1.3spring 容器配置
在config包下定义ApplicationConfig.java,它对应Web.xml中contextLoderListener配置
//springbean.xml
package com.wangjunji.security.springmvc.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.stereotype.Controller; @Configuration @ComponentScan(basePackages = "com.wangjunji.security.springmvc", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)}) public class ApplicationConfig { //在此配置除了controller的其它bean,比如:数据库链接池,事务管理器,业务bean }
1.4 servletContext配置
本案例采用servlet 3.0无web.xml方式的conifg包下定webConfig.java,它对应于dispatcherSevlet配置
//springmvc.xml
package com.wangjunji.security.springmvc.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.stereotype.Controller; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @EnableWebMvc @ComponentScan(basePackages = "com.wangjunji.security.springmvc", includeFilters = {@ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class)}) public class WebConfig implements WebMvcConfigurer { @Bean public InternalResourceViewResolver viewResolver(){ InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/views"); //需要注意,需要创建相应的目录 viewResolver.setSuffix(".jsp"); return viewResolver; } }
1.5加载spring容器
在init包下定义spring容器初始化类springApplicatinlnitIaizer,此类实现WebApplicationInitializer接口,spring 容器启动时加载webappliction所有接口的所有实现类
package com.wangjunji.security.springmvc.init; import com.wangjunji.security.springmvc.config.ApplicationConfig; import com.wangjunji.security.springmvc.config.WebConfig; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; //这个就相当于Web.xml public class SpringApplicationInitalizer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class[] getRootConfigClasses() { return new Class[]{ApplicationConfig.class}; } @Override protected Class[] getServletConfigClasses() { return new Class[] {WebConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
1.6认证页面
在Webapp/WEB-INF/views下定义认证页面login.jsp,本案例只是测试认证流程,页面没有添加css样式,页面实现可填 入用户名,密码,触发登录将提交表单信息至/login,内容如下
在WebConfig.java中新增如下配置,将直接导向login.jsp页面
@Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("login"); }
启动项目,访问、路径
1.7 认证接口
先创建登陆用户实体类
package com.wangjunji.security.springmvc.model; import lombok.Data; public class AuthenticationRequest { //认证请求参数,账号,密码 /** * 用户名 */ private String username; /** * 用户密码 */ private String password; 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; } }
//再创建用户账号实体类,数据库里面的
package com.wangjunji.security.springmvc.model; import lombok.Data; public class UserDto { //用户的身份 private String id; private String username; private String password; private String fullname; private String mobile; public UserDto(String id, String username, String password, String fullname, String mobile) { this.id = id; this.username = username; this.password = password; this.fullname = fullname; this.mobile = mobile; } public String getId() { return id; } public void setId(String 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 getFullname() { return fullname; } public void setFullname(String fullname) { this.fullname = fullname; } public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile = mobile; } }
//创建认证接口
package com.wangjunji.security.springmvc.service; import com.wangjunji.security.springmvc.model.AuthenticationRequest; import com.wangjunji.security.springmvc.model.UserDto; /** * create by wangjunji */ public interface AuthenticationService { /** * 用户认证 * @param authenticationRequest 用户请求,账号和密码 */ UserDto authentication(AuthenticationRequest authenticationRequest); }
//创建认证实体类
package com.wangjunji.security.springmvc.service; import com.wangjunji.security.springmvc.model.AuthenticationRequest; import com.wangjunji.security.springmvc.model.UserDto; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.HashMap; import java.util.Map; @Service public class AuthenticationServiceImpl implements AuthenticationService { /** * 用户认证,校验用户身份信息 * @param authenticationRequest 用户请求,账号和密码 * @return */ @Override public UserDto authentication(AuthenticationRequest authenticationRequest) { if(authenticationRequest == null || StringUtils.isEmpty(authenticationRequest.getUsername()) ||StringUtils.isEmpty(authenticationRequest.getPassword())){ throw new RuntimeException("账号或密码为空"); } UserDto userDto = getUserDto(authenticationRequest.getUsername()); if(userDto == null){ throw new RuntimeException("用户不存在"); } if(! userDto.getPassword().equals( authenticationRequest.getPassword())){ throw new RuntimeException("密码不正确"); } return userDto; } //模拟用户查询 public UserDto getUserDto(String name){ return userMap.get(name); } //用户信息 private MapuserMap = new HashMap<>(); { userMap.put("zhangsan",new UserDto("1010","zhangsan","123","张三","133443")); userMap.put("lisi",new UserDto("1011","1isi","456","李四","144553")); } }
//创建登陆controller
package com.wangjunji.security.springmvc.controller; import com.wangjunji.security.springmvc.model.AuthenticationRequest; import com.wangjunji.security.springmvc.model.UserDto; import com.wangjunji.security.springmvc.service.AuthenticationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class LoginController { @Autowired AuthenticationService authenticationService; @RequestMapping(value="/login") public String login(AuthenticationRequest authenticationRequest){ UserDto authentication = authenticationService.authentication(authenticationRequest); return authentication.getUsername()+"登陆成功"; } }
1.8 实现会话功能
会话是指用户登入系统后,系统会记住该用户的登陆状态,它可以连续操作直到退出系统的过程
认证的目的是对系统资源的保护,每次对资源的访问,系统必须得知道谁在访问资源,才能对该请求进行合法性拦截。因此,在认证成功后,一会把认证成功的用户信息放入sessionk ,在后续的请求中,系统能够从Session中获取当前用户,用这样的方式来实现会话机制。
(1)增加会话控制
首先在UserDao中定义一个SESSION_USER_KEY,作为Session中存放登录用户信息的key
public static final String SEESION_USER_KEY = "_user";
然后修改LoginController,认证成功后,把用户信息放入当前会话。并增加用户登出方法,登出时将session置为失效
package com.wangjunji.security.springmvc.controller; import com.wangjunji.security.springmvc.model.AuthenticationRequest; import com.wangjunji.security.springmvc.model.UserDto; import com.wangjunji.security.springmvc.service.AuthenticationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpSession; @RestController public class LoginController { @Autowired AuthenticationService authenticationService; @RequestMapping(value="/login",produces = {"text/plain;charset=UTF-8"}) public String login(AuthenticationRequest authenticationRequest, HttpSession httpSession){ UserDto authentication = authenticationService.authentication(authenticationRequest); httpSession.setAttribute(UserDto.SEESION_USER_KEY,authentication); return authentication.getUsername()+"登陆成功"; } @GetMapping(value = "/session",produces = {"text/plain;charset=UTF-8"}) public String getSession(HttpSession session){ Object attribute = session.getAttribute(UserDto.SEESION_USER_KEY); if(attribute == null){ return "匿名访问"; }else { UserDto attribute1 = (UserDto) attribute; return attribute1.getFullname(); } } @GetMapping(value = "/logout",produces = {"text/plain;charset=UTF-8"}) public String invalidateSession(HttpSession session){ session.invalidate(); return "退出"; } }
2.0实现授权
第一步:给用户进行权限赋值
package com.wangjunji.security.springmvc.model; import lombok.Data; import java.util.Set; public class UserDto { //用户的身份 public static final String SEESION_USER_KEY = "_user"; private String id; private String username; private String password; private String fullname; private String mobile; private Setauthoritites; public SetgetAuthoritites() { return authoritites; } public void setAuthoritites(Setauthoritites) { this.authoritites = authoritites; } public UserDto(String id, String username, String password, String fullname, String mobile, Setauthoritites) { this.id = id; this.username = username; this.password = password; this.fullname = fullname; this.mobile = mobile; this.authoritites = authoritites; } public String getId() { return id; } public void setId(String 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 getFullname() { return fullname; } public void setFullname(String fullname) { this.fullname = fullname; } public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile = mobile; } }
第二步:给用户增加相应的权限
package com.wangjunji.security.springmvc.service; import com.wangjunji.security.springmvc.model.AuthenticationRequest; import com.wangjunji.security.springmvc.model.UserDto; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @Service public class AuthenticationServiceImpl implements AuthenticationService { /** * 用户认证,校验用户身份信息 * @param authenticationRequest 用户请求,账号和密码 * @return */ @Override public UserDto authentication(AuthenticationRequest authenticationRequest) { if(authenticationRequest == null || StringUtils.isEmpty(authenticationRequest.getUsername()) ||StringUtils.isEmpty(authenticationRequest.getPassword())){ throw new RuntimeException("账号或密码为空"); } UserDto userDto = getUserDto(authenticationRequest.getUsername()); if(userDto == null){ throw new RuntimeException("用户不存在"); } if(! userDto.getPassword().equals( authenticationRequest.getPassword())){ throw new RuntimeException("密码不正确"); } return userDto; } //模拟用户查询 public UserDto getUserDto(String name){ return userMap.get(name); } //用户信息 private MapuserMap = new HashMap<>(); { Setauthoritites1 = new HashSet<>(); Setauthoritites2 = new HashSet<>(); authoritites1.add("p1"); //这个p1我们人为让它和/r/r1对应 authoritites2.add("p2"); //这个p2我们人为让它和/r/r2对应 userMap.put("zhangsan",new UserDto("1010","zhangsan","123","张三","133443",authoritites1)); userMap.put("lisi",new UserDto("1011","1isi","456","李四","144553",authoritites2)); } }
第三步:创建conrtoller
package com.wangjunji.security.springmvc.controller; import com.wangjunji.security.springmvc.model.AuthenticationRequest; import com.wangjunji.security.springmvc.model.UserDto; import com.wangjunji.security.springmvc.service.AuthenticationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpSession; @RestController public class LoginController { @Autowired AuthenticationService authenticationService; @RequestMapping(value="/login",produces = {"text/plain;charset=UTF-8"}) public String login(AuthenticationRequest authenticationRequest, HttpSession httpSession){ UserDto authentication = authenticationService.authentication(authenticationRequest); httpSession.setAttribute(UserDto.SEESION_USER_KEY,authentication); return authentication.getUsername()+"登陆成功"; } @GetMapping(value = "/session",produces = {"text/plain;charset=UTF-8"}) public String getSession(HttpSession session){ Object attribute = session.getAttribute(UserDto.SEESION_USER_KEY); if(attribute == null){ return "匿名访问"; }else { UserDto attribute1 = (UserDto) attribute; return attribute1.getFullname(); } } @GetMapping(value = "/logout",produces = {"text/plain;charset=UTF-8"}) public String invalidateSession(HttpSession session){ session.invalidate(); return "退出"; } @GetMapping(value = "/r/r1",produces = {"text/plain;charset=UTF-8"}) public String rr1(HttpSession session){ Object attribute = session.getAttribute(UserDto.SEESION_USER_KEY); if(attribute == null){ return "匿名访问"; }else { UserDto attribute1 = (UserDto) attribute; return attribute1.getFullname()+"访问/r/r1"; } } @GetMapping(value = "/r/r2",produces = {"text/plain;charset=UTF-8"}) public String rr2(HttpSession session){ Object attribute = session.getAttribute(UserDto.SEESION_USER_KEY); if(attribute == null){ return "匿名访问"; }else { UserDto attribute1 = (UserDto) attribute; return attribute1.getFullname()+"访问/r/r2"; } } }
第四步,创建拦截器
package com.wangjunji.security.springmvc.interceptor; import com.wangjunji.security.springmvc.model.UserDto; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @Component //拦载器 public class SimpleAuthticationInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //这个方法中校验用户请求的url是否在用户请求的权限范围内 //取用户身份 response.setContentType("text/html;charset=utf-8"); Object attribute = request.getSession().getAttribute(UserDto.SEESION_USER_KEY); if(attribute == null){ //没有认证,提示登录 writeContent(response,"请登录"); } UserDto attribute1 = (UserDto) attribute; //请示的url String reqestURL = request.getRequestURI(); if(attribute1.getAuthoritites().contains("p1") && reqestURL.contains("/r/r1")){ return true; } if(attribute1.getAuthoritites().contains("p2") && reqestURL.contains("/r/r2")){ return true; } writeContent(response ,"没有权限"); return false; } private void writeContent(HttpServletResponse response,String msg) throws IOException { response.setContentType("text/html;charset=utf-8"); PrintWriter writer = response.getWriter(); writer.print(msg); writer.close(); response.resetBuffer(); } }
第五步:注册拦截器
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(simpleAuthticationInterceptor).addPathPatterns("/r/**"); } //注意需要指定拦载的url