第四课主要是基于 redis 实现单点登录。早期互联网应用由于用户少、访问量低,多个系统的代码可以部署在一台机器中,所以 session 可以直接共享。但是随着互联网的发展,现如今大型网站均是分布式系统,session 无法再像以前那样共享。
单点登录就是在这样多系统共存的环境下,实现一次登录,多次访问不同的互信的系统,单点登录可以简化用户操作,提高友好性。单点登录可以有多种实现方式,本实验采用 token+ redis 方式实现。在用户第一次访问后端接口时直接跳到登录页面,使用账号、密码验证用户真实性,验证成功后,后端返回给用户随机产生的 token(存储在 redis 中)。随后,用户访问后端的接口都要携带这个 token,使用 token 来验证用户的有效性。用户注销时,token 从redis 中删除。
RedisConfig.java
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings ( "all" )
public RedisTemplate< String, Object> redisTemplate ( RedisConnectionFactory factory) {
RedisTemplate< String, Object> template = new RedisTemplate < String, Object> ( ) ;
template. setConnectionFactory ( factory) ;
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer ( Object. class ) ;
ObjectMapper om = new ObjectMapper ( ) ;
om. setVisibility ( PropertyAccessor. ALL, JsonAutoDetect. Visibility. ANY) ;
om. enableDefaultTyping ( ObjectMapper. DefaultTyping. NON_FINAL) ;
jackson2JsonRedisSerializer. setObjectMapper ( om) ;
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer ( ) ;
template. setKeySerializer ( stringRedisSerializer) ;
template. setHashKeySerializer ( stringRedisSerializer) ;
template. setValueSerializer ( jackson2JsonRedisSerializer) ;
template. setHashValueSerializer ( jackson2JsonRedisSerializer) ;
template. afterPropertiesSet ( ) ;
return template;
}
}
WebConfig.java
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors ( InterceptorRegistry registry) {
registry. addInterceptor ( loginInterceptor) . addPathPatterns ( "/user/**" ) . excludePathPatterns ( "/user/login" ) ;
super . addInterceptors ( registry) ;
}
}
UserController.java
@RestController
@RequestMapping ( "user" )
public class UserController {
@Autowired
private UserService userService;
@PostMapping ( "login" )
public ResultObject login ( @RequestBody User user) {
if ( user == null) {
return new ResultObject ( - 1 , "param empty" , null) ;
}
ResultObject resultObject = userService. login ( user) ;
return resultObject;
}
@GetMapping ( "test" )
public ResultObject test ( ) {
ResultObject resultObject = new ResultObject ( 1 , "test success" , null) ;
return resultObject;
}
@GetMapping ( "logout" )
public ResultObject logout ( @RequestParam String token) {
ResultObject resultObject = userService. logout ( token) ;
return resultObject;
}
}
UserMapper.java
@Mapper
public interface UserMapper {
@Select ( "select id, name, gender, age, password from user " +
" where name = #{name} and password = #{password}" )
User select ( User user) ;
}
ResultObject.java
public class ResultObject {
private int code;
private String msg;
private Object result;
public ResultObject ( ) {
super ( ) ;
}
public ResultObject ( int code, String msg, Object result) {
super ( ) ;
this . code = code;
this . msg = msg;
this . result = result;
}
public int getCode ( ) {
return code;
}
public void setCode ( int code) {
this . code = code;
}
public String getMsg ( ) {
return msg;
}
public void setMsg ( String msg) {
this . msg = msg;
}
public Object getResult ( ) {
return result;
}
public void setResult ( Object result) {
this . result = result;
}
@Override
public String toString ( ) {
return "ResultObject [code=" + code + ", msg=" + msg + ", result=" + result + "]" ;
}
}
User.java
@JsonInclude ( JsonInclude. Include. NON_DEFAULT)
public class User implements Serializable {
private static final long serialVersionUID = 1 L;
private int id;
private String name;
private int gender;
private int age;
private String password;
public User ( ) {
super ( ) ;
}
public User ( String name, int gender, int age, String password) {
super ( ) ;
this . name = name;
this . gender = gender;
this . age = age;
this . password = password;
}
public int getId ( ) {
return id;
}
public void setId ( int id) {
this . id = id;
}
public String getName ( ) {
return name;
}
public void setName ( String name) {
this . name = name;
}
public int getGender ( ) {
return gender;
}
public void setGender ( int gender) {
this . gender = gender;
}
public int getAge ( ) {
return age;
}
public void setAge ( int age) {
this . age = age;
}
public String getPassword ( ) {
return password;
}
public void setPassword ( String password) {
this . password = password;
}
@Override
public String toString ( ) {
return "User [id=" + id + ", name=" + name + ", gender=" + gender + ", age=" + age + ", password=" + password + "]" ;
}
}
LoginInterceptor.java
@Component
public class LoginInterceptor implements HandlerInterceptor {
private static final Log logger = LogFactory. getLog ( LoginInterceptor. class ) ;
@Autowired
private RedisUtil redisUtil;
@Override
public boolean preHandle ( HttpServletRequest request, HttpServletResponse response, Object handler) {
try {
String token = request. getParameter ( "token" ) ;
if ( token == null) {
render ( response, "lack token" ) ;
return false ;
}
User user = ( User) redisUtil. get ( token) ;
if ( user != null) {
Object preToken = redisUtil. get ( Integer. toString ( user. getId ( ) ) ) ;
if ( ! token. equals ( preToken) ) {
render ( response, "another user login" ) ;
return false ;
} else {
Long expire = redisUtil. getExpire ( token) ;
if ( expire < 60 * 1000 ) {
redisUtil. expire ( token, 60 * 30 ) ;
}
}
} else {
render ( response, "token error" ) ;
return false ;
}
} catch ( Exception e) {
logger. info ( "preHandle=" + e) ;
}
return true ;
}
@Override
public void postHandle ( HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion ( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
public void render ( HttpServletResponse response, String msg) throws IOException {
ResultObject resultObject = new ResultObject ( - 1 , msg, null) ;
JSONObject object = new JSONObject ( resultObject) ;
response. setContentType ( "application/json;charset=UTF-8" ) ;
OutputStream out = response. getOutputStream ( ) ;
out. write ( object. toString ( ) . getBytes ( "UTF-8" ) ) ;
out. flush ( ) ;
out. close ( ) ;
}
}
UserService.java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisUtil redisUtil;
public ResultObject login ( User user) {
ResultObject resultObject = new ResultObject ( ) ;
User fullUser = userMapper. select ( user) ;
if ( fullUser == null) {
resultObject. setCode ( - 1 ) ;
resultObject. setMsg ( "name or password error" ) ;
return resultObject;
}
String token = UUID. randomUUID ( ) . toString ( ) ;
int interval = 60 * 5 ;
redisUtil. set ( token, fullUser, interval) ;
redisUtil. set ( Integer. toString ( fullUser. getId ( ) ) , token, interval) ;
resultObject. setCode ( 1 ) ;
resultObject. setMsg ( "login success" ) ;
Map< String, Object> map = new HashMap < > ( ) ;
map. put ( "token" , token) ;
fullUser. setPassword ( null) ;
map. put ( "user" , fullUser) ;
resultObject. setResult ( map) ;
return resultObject;
}
public ResultObject logout ( String token) {
User user = ( User) redisUtil. get ( token) ;
redisUtil. del ( Integer. toString ( user. getId ( ) ) ) ;
redisUtil. del ( token) ;
return new ResultObject ( 1 , "success" , null) ;
}
}
RedisUtil.java
@Component
public class RedisUtil {
@Autowired
private RedisTemplate< String, Object> redisTemplate;
public boolean expire ( String key, long time) {
try {
if ( time > 0 ) {
redisTemplate. expire ( key, time, TimeUnit. SECONDS) ;
}
return true ;
} catch ( Exception e) {
e. printStackTrace ( ) ;
return false ;
}
}
public long getExpire ( String key) {
return redisTemplate. getExpire ( key, TimeUnit. SECONDS) ;
}
public boolean hasKey ( String key) {
try {
return redisTemplate. hasKey ( key) ;
} catch ( Exception e) {
e. printStackTrace ( ) ;
return false ;
}
}
@SuppressWarnings ( "unchecked" )
public void del ( String. . . key) {
if ( key != null && key. length > 0 ) {
if ( key. length == 1 ) {
redisTemplate. delete ( key[ 0 ] ) ;
} else {
redisTemplate. delete ( CollectionUtils. arrayToList ( key) ) ;
}
}
}
public Object get ( String key) {
return key == null ? null : redisTemplate. opsForValue ( ) . get ( key) ;
}
public boolean set ( String key, Object value) {
try {
redisTemplate. opsForValue ( ) . set ( key, value) ;
return true ;
} catch ( Exception e) {
e. printStackTrace ( ) ;
return false ;
}
}
public boolean set ( String key, Object value, long time) {
try {
if ( time > 0 ) {
redisTemplate. opsForValue ( ) . set ( key, value, time, TimeUnit. SECONDS) ;
} else {
set ( key, value) ;
}
return true ;
} catch ( Exception e) {
e. printStackTrace ( ) ;
return false ;
}
}
public long incr ( String key, long delta) {
if ( delta < 0 ) {
throw new RuntimeException ( "delta must > 0" ) ;
}
return redisTemplate. opsForValue ( ) . increment ( key, delta) ;
}
public long decr ( String key, long delta) {
if ( delta < 0 ) {
throw new RuntimeException ( "delta must > 0" ) ;
}
return redisTemplate. opsForValue ( ) . increment ( key, - delta) ;
}
}